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,5 +1,7 @@
1
1
  import { classifyModules } from "./lib/module-tier.js";
2
2
  import { MODULE_SLICES, normalizeModules, } from "./module-architecture.js";
3
+ import { ALIASES, SHAPE_ROSTERS, SHAPE_STARTER_ALIASES, } from "./starter-grid.js";
4
+ import { RECOGNIZED_TEMPLATES } from "./template-names.js";
3
5
  /**
4
6
  * The closed **shape** enum (starter-template contract § The three axes,
5
7
  * operator-disposed 2026-09-24) — `<primary node>-<offering node>` in
@@ -17,28 +19,13 @@ export const STARTER_SHAPES = [
17
19
  ];
18
20
  /**
19
21
  * The closed **design** enum (same contract section) — the look applied to
20
- * any shape; orthogonal to it by construction.
22
+ * any shape; orthogonal to it by construction. Each name RESOLVES to a
23
+ * package at `site-templates/designs/<name>/` (#6813 D2/D3, D11 pins this set
24
+ * equal to that directory listing): `resolveDesign` (`design-scaffold.ts`)
25
+ * reads the package's own `layout`, `colors` and treatments — no design fact
26
+ * lives in this module, or anywhere in `packages/create-ailk/src`, any more.
21
27
  */
22
28
  export const STARTER_DESIGNS = ["standard", "minimalism"];
23
- /**
24
- * Each design's layout knobs (#6638) — the part of a design that is chrome,
25
- * so a template names `design:` instead of re-typing a `layout` block, and any
26
- * shape can wear any design. `resolveSiteChrome(layout, design)` reads it:
27
- * an explicit `TemplateManifest.layout` wins field by field, then this, then
28
- * `DEFAULT_SITE_CHROME`. `standard` declares nothing — it IS the defaults, so
29
- * spelling them out here would be a second copy of them to drift. A plain
30
- * object, not a file: this module touches no disk. The design's documented
31
- * look lives in `site-templates/designs/<design>/DESIGN.md`.
32
- */
33
- export const DESIGN_LAYOUTS = {
34
- standard: {},
35
- minimalism: {
36
- nav: "mark-only",
37
- footer: "centered",
38
- announcementBanner: false,
39
- home: "deck",
40
- },
41
- };
42
29
  /**
43
30
  * The base layer's directory under `site-templates/` (#5618 D1) — the seed
44
31
  * files every template reaches by declaring `extends`, installed by
@@ -69,386 +56,178 @@ export class TemplateError extends Error {
69
56
  }
70
57
  }
71
58
  /**
72
- * The three pages every starter carries whatever its shape (#6658 D5,
73
- * `docs/decisions/starter-roster-page-set.md` D4/D7): a glossary, a newsletter
74
- * signup and a book-a-call page. Each is a module-less `GATEABLE_SURFACES`
75
- * entry the roster enables, with its seed in `_base`. A template carrying the
76
- * scheduling module overrides `BookCall/book-call.mdx` in its own tree, so the
77
- * call to action opens `/meet` rather than `/contact`.
59
+ * Compose one alias into its manifest (#6753 D5, extended #6757 D7): the
60
+ * shape's standard roster, minus the alias's `drop` routes, with its `add`
61
+ * rows inserted before the `before` route (appended when there is none),
62
+ * then each `insert` group's rows spliced before ITS OWN `before` route, in
63
+ * array order — the one alias with more than one anchor point
64
+ * (`minimalism-site`) uses this to interleave its own rows into the shape's
65
+ * row order. Rows are pushed by reference, never cloned, so
66
+ * `manifest.roster.includes(row)` identity checks downstream keep working.
67
+ * Every refusal names the alias, so a malformed table fails at load — the
68
+ * shipped one — or in the test that injected it.
69
+ *
70
+ * @throws TemplateError on a `drop`, `before` or an `insert` group's `before`
71
+ * naming a route the shape (or the roster built so far) does not carry, a
72
+ * `before` with nothing to add, an `insert` group with empty `rows`, an
73
+ * `add`/`insert` row whose route a kept shape row already claims, or two
74
+ * `add`/`insert` rows naming one route.
78
75
  */
79
- const SHAPE_INDEPENDENT_ROWS = [
80
- { route: "/glossary", content: ["content/en/GlossaryList/glossary.mdx"] },
81
- {
82
- route: "/newsletter",
83
- content: ["content/en/NewsletterSignup/newsletter.mdx"],
84
- },
85
- { route: "/book-a-call", content: ["content/en/BookCall/book-call.mdx"] },
86
- ];
76
+ function composeManifest(name, alias) {
77
+ const rows = SHAPE_ROSTERS[alias.shape];
78
+ const { drop = [], add = [], before, insert = [] } = alias.differences ?? {};
79
+ for (const route of drop) {
80
+ if (!rows.some((row) => row.route === route)) {
81
+ throw new TemplateError(`alias "${name}": differences.drop names "${route}", which shape "${alias.shape}" does not carry`);
82
+ }
83
+ }
84
+ const kept = rows.filter((row) => !drop.includes(row.route));
85
+ let roster;
86
+ if (before === undefined) {
87
+ roster = [...kept, ...add];
88
+ }
89
+ else {
90
+ const at = kept.findIndex((row) => row.route === before);
91
+ if (at === -1) {
92
+ throw new TemplateError(`alias "${name}": differences.before names "${before}", which shape "${alias.shape}" does not carry after its drops`);
93
+ }
94
+ if (add.length === 0) {
95
+ throw new TemplateError(`alias "${name}": differences.before names "${before}" but differences.add is empty`);
96
+ }
97
+ roster = [...kept.slice(0, at), ...add, ...kept.slice(at)];
98
+ }
99
+ for (const group of insert) {
100
+ const at = roster.findIndex((row) => row.route === group.before);
101
+ if (at === -1) {
102
+ throw new TemplateError(`alias "${name}": differences.insert names "${group.before}", which shape "${alias.shape}" does not carry after its drops`);
103
+ }
104
+ if (group.rows.length === 0) {
105
+ throw new TemplateError(`alias "${name}": differences.insert names "${group.before}" but its rows is empty`);
106
+ }
107
+ roster = [...roster.slice(0, at), ...group.rows, ...roster.slice(at)];
108
+ }
109
+ const shapeRoutes = new Set(kept.map((row) => row.route));
110
+ const added = new Set();
111
+ for (const row of [...add, ...insert.flatMap((group) => group.rows)]) {
112
+ if (shapeRoutes.has(row.route)) {
113
+ throw new TemplateError(`route "${row.route}" is claimed by both shape "${alias.shape}" and alias "${name}"`);
114
+ }
115
+ if (added.has(row.route)) {
116
+ throw new TemplateError(`alias "${name}": differences.add names route "${row.route}" twice`);
117
+ }
118
+ added.add(row.route);
119
+ }
120
+ return {
121
+ name,
122
+ requires: alias.modules,
123
+ ...(alias.layout && { layout: alias.layout }),
124
+ categories: alias.categories,
125
+ shape: alias.shape,
126
+ design: alias.design,
127
+ extends: BASE_LAYER_DIR,
128
+ roster,
129
+ };
130
+ }
87
131
  /**
88
- * The rows every `organization-product` template brings itself — one declared
89
- * page set, whichever layer carries each seed. Re-derived by #6658 from the
90
- * standard (`derive-roster.py --entity-type software --offering-type software
91
- * --primary-goal convert --scale global`, the `--shape organization-product`
92
- * equivalent), edited only by the deviations the Free manifest's § 3a records
93
- * with their reasons. With a template named, `scaffold()` keeps a `_base` or
94
- * overlay seed only when a merged roster row names it (#6646 D1): the roster
95
- * decides what installs, and the manifest generator's `--check` proves it.
96
- *
97
- * Two layers, one roster:
98
- *
99
- * - `/`, `/software/[slug]`, `/use-cases`, `/about`, `/faq`, `/contact` and
100
- * `/purchase-policy` take their content from the overlay
101
- * (`template-overlay/content/en/**`, copied whole into `templates/` by
102
- * `build-templates.mjs`). `/software/[slug]` is the product's own detail
103
- * page (`/software/product`, a SoftwareProduct), which replaced the
104
- * `/features` stand-in (#6658 D3); its route is core, like `/about`.
105
- * `/use-cases` lists the three UseCase pages at `/use-cases/[slug]`
106
- * (#6658 D4).
107
- * - `/pricing`, `/privacy`, `/terms`, `/glossary`, `/newsletter`,
108
- * `/book-a-call` and the three UseCase pages take their content from the
109
- * base layer (`site-templates/_base/content/**`, #5618 D1), which every
110
- * template reaches by declaring `extends` — installed before the module
111
- * bundles and the template's own tree, so a template may override a base
112
- * page by shipping the same path. Their surfaces are module-less by
113
- * construction (parent § D16, #6658 D5): a template enables them by
114
- * NAMING them in its roster — `scaffold()` selects their
115
- * `GATEABLE_SURFACES` entries from the merged roster's routes (#5469 D10).
132
+ * The declared templates, each composed once at load from its `ALIASES` row
133
+ * (#6753) — no manifest here is hand-typed, so a template's shape, design,
134
+ * modules and roster have one authority: `starter-grid.ts`. Declaration order
135
+ * is `ALIASES`'s, which is `RECOGNIZED_TEMPLATES`'s.
116
136
  *
117
- * `/software`, `/sign-in` and `/sign-up` carry no `content`: they are code
118
- * routes (the SoftwareProduct listing, the SignInCard island, the sign-up
119
- * form), like the API-backed rows the scheduling module contributes — listed
120
- * so the manifest is a complete install record, where the generator marks
121
- * them `kind: route` (#6646 D2).
122
- *
123
- * `/purchase-policy` is the product-shape purchase policy (#6646 D3): its
124
- * overlay seed carries returns, shipping and refunds sections — the overlay,
125
- * not `_base`, because the route is core and a scaffold naming no template
126
- * serves it too. A service-shape template overrides that seed with its own
127
- * cancellation and refunds file.
128
- *
129
- * `/blog` is deliberately NOT here (#5469 D3): the docs-blog module
130
- * contributes its own `/blog` row, so a base row would make `mergeRoster`
131
- * refuse every template that takes that module. The Free teaser row is
132
- * declared on `marketing-site-free` alone, below.
137
+ * Frozen (#5507): this registry is also the issuer's authorization allowlist
138
+ * (spec 5480 § C7) — a mutable global there could be monkey-patched at
139
+ * runtime. A caller needing a different registry for a test injects its own
140
+ * fixture rather than mutating this one.
133
141
  */
134
- const TEMPLATE_BASE_ROSTER = [
135
- { route: "/", content: ["content/en/HomePage/home.mdx"] },
136
- { route: "/software" },
137
- { route: "/software/[slug]", contentDir: "content/en/SoftwareProduct" },
138
- {
139
- route: "/use-cases",
140
- content: ["content/en/UseCasesList/use-cases.mdx"],
141
- },
142
- { route: "/use-cases/[slug]", contentDir: "content/en/UseCase" },
143
- { route: "/pricing", content: ["content/en/Pricing/pricing.mdx"] },
144
- { route: "/about", content: ["content/en/AboutPage/about.mdx"] },
145
- { route: "/faq", content: ["content/en/FAQPage/faq.mdx"] },
146
- { route: "/contact", content: ["content/en/ContactPage/contact.mdx"] },
147
- ...SHAPE_INDEPENDENT_ROWS,
148
- { route: "/sign-in" },
149
- { route: "/sign-up" },
150
- { route: "/privacy", content: ["content/en/PrivacyPage/privacy.mdx"] },
151
- { route: "/terms", content: ["content/en/TermsPage/terms.mdx"] },
152
- {
153
- route: "/purchase-policy",
154
- content: ["content/en/PurchasePolicyPage/purchase-policy.mdx"],
155
- },
156
- ];
142
+ export const TEMPLATE_MANIFESTS = Object.freeze(Object.fromEntries(RECOGNIZED_TEMPLATES.map((name) => [
143
+ name,
144
+ composeManifest(name, ALIASES[name]),
145
+ ])));
157
146
  /**
158
- * The Free `/blog` teaser (#5469 D3) — one static BlogList page (schema `Blog`
159
- * with an empty `ItemList`; the correct page type, not a LandingPage) that
160
- * says what the Pro docs-blog module gives you. It is served by the generic
161
- * `[slug]` route — which probes `LandingPage` first, then `BlogList` — over
162
- * `content/en/BlogList/blog.mdx`, which ships in the always-copied overlay. `/blog` degrades by DIRECTORY PRECEDENCE, not
163
- * by a module gate: in a keyed docs-blog scaffold the module's own
164
- * `blog/[[...slug]]/page.tsx` directory exists and Next.js resolves the static
165
- * segment first, so the teaser file is inert; keyless, no `blog/` directory
166
- * exists and the teaser renders. Declared on the Free template only — on
167
- * `marketing-site` the docs-blog contribution claims the route.
147
+ * The composed FREE manifest per shape with no `--template` alias of its own
148
+ * (#6757 D5) — the coordinates a keyless `--shape S` builds. `Object.freeze`d,
149
+ * composed once at load by the same module-private `composeManifest` over
150
+ * `SHAPE_STARTER_ALIASES`, each manifest's `name` the shape. Data holding
151
+ * composed manifests, the status D5 gives `TEMPLATE_MANIFESTS`;
152
+ * `resolveTemplate` stays the only exported function that yields a
153
+ * template's roster, and its behavior is unchanged — it does NOT resolve a
154
+ * shape name (NFR-3, the issuer allowlist stays `TEMPLATE_MANIFESTS`-only).
168
155
  */
169
- const FREE_BLOG_TEASER_ROW = {
170
- route: "/blog",
171
- content: ["content/en/BlogList/blog.mdx"],
172
- };
156
+ export const SHAPE_STARTERS = Object.freeze(Object.fromEntries(Object.entries(SHAPE_STARTER_ALIASES).map(([name, alias]) => [
157
+ name,
158
+ composeManifest(name, alias),
159
+ ])));
173
160
  /**
174
- * The rows for the `signal-collectors` bundle's six sample pages (#6646
175
- * disposition (a)) — the project listing, its two project pages and the
176
- * three thank-you pages its `installMap` lands. The module contributes no
177
- * row for any of these routes (`module-architecture.ts`'s installMap
178
- * comment), so a template that takes the module declares them, and the
179
- * roster names every page the scaffold installs. `/projects` is a
180
- * module-less `GATEABLE_SURFACES` entry, so naming it here also copies the
181
- * route directory the samples render through (`rosterSurfaces`).
161
+ * Where a starter's files live under `site-templates/` (#6757 D5) — the one
162
+ * place that says so. A `SHAPE_STARTERS` key (a bare-shape starter with no
163
+ * `--template` alias) lives at `shapes/<name>`, out of the template showcase
164
+ * (§ 1.6). A declared `--template` alias whose `ownTree` is `false` (spec
165
+ * 6892 D1) ships no directory of its own: its seed layer is its shape's tree.
166
+ * Every other name (a declared alias with its own tree) is unchanged.
182
167
  */
183
- const SIGNAL_COLLECTORS_SAMPLE_ROWS = [
184
- { route: "/projects", content: ["content/en/ProjectsList/projects.mdx"] },
185
- { route: "/projects/[slug]", contentDir: "content/en/Project" },
186
- { route: "/thanks/[slug]", contentDir: "content/en/ThankYouPage" },
187
- ];
168
+ export function templateTreeDir(name) {
169
+ if (Object.prototype.hasOwnProperty.call(SHAPE_STARTERS, name)) {
170
+ return `shapes/${name}`;
171
+ }
172
+ const alias = ALIASES[name];
173
+ if (alias?.ownTree === false) {
174
+ return `shapes/${alias.shape}`;
175
+ }
176
+ return name;
177
+ }
188
178
  /**
189
- * `minimalism-site`'s own rows (#5589) — the one declared template that does
190
- * not reuse `TEMPLATE_BASE_ROSTER`. Its page set is the issue's ROSTER, and
191
- * the rows it does NOT carry are as deliberate as the ones it does: no
192
- * `/how-it-works`, product page or `/pricing` (a reports-first site sells
193
- * nothing from a pricing table), and no `/sign-in` (a code route every
194
- * scaffold carries regardless — `CORE_ROUTE_DIRS` — which this template
195
- * simply does not advertise as one of its pages). It does carry the three
196
- * pages every starter carries (`SHAPE_INDEPENDENT_ROWS`, #6658), with its
197
- * own `/book-a-call` seed pointing at the scheduling module's `/meet`.
198
- *
199
- * `/purchase-policy` is the service-shape purchase policy (#6646 D3) — this
200
- * template's own cancellation and refunds seed, overriding `_base`'s.
179
+ * Whether `name` ships its own `site-templates/<name>/` directory (spec 6892
180
+ * D1) — `false` only for a declared `--template` alias whose `ownTree` is
181
+ * `false` (`minimalism-site` today). A `SHAPE_STARTERS` key owns its
182
+ * `shapes/<name>` tree outright, so it is never treeless by this predicate
183
+ * even though `templateTreeDir` also renames its directory.
201
184
  *
202
- * `/meet`, `/meet/[slug]`, `/meet/confirmed`, `/reports/[slug]` and the three
203
- * `/flows/*` rows are absent here on purpose: they are the `scheduling` and
204
- * `signal-collectors` modules' own `pageContributions`, merged in by
205
- * `mergeRoster`. Declaring one here would make that merge refuse the
206
- * template outright (§ D8's both-claimants refusal).
185
+ * The one place a caller outside this module (or `template-names.ts`) asks
186
+ * "does this name have its own tree" without needing `ALIASES` itself —
187
+ * `starter-grid.ts`'s own header restricts its importers to this file and
188
+ * `template-names.ts`, so a producer script asking this question goes
189
+ * through this predicate rather than importing `ALIASES` directly.
207
190
  */
208
- const MINIMALISM_ROSTER = [
209
- { route: "/", content: ["content/en/HomePage/home.mdx"] },
210
- { route: "/reports", content: ["content/en/LandingPage/reports.mdx"] },
211
- { route: "/about", content: ["content/en/AboutPage/about.mdx"] },
212
- { route: "/services", content: ["content/en/ServicesList/services.mdx"] },
213
- { route: "/services/[slug]", contentDir: "content/en/Service" },
214
- { route: "/contact", content: ["content/en/ContactPage/contact.mdx"] },
215
- { route: "/referral", content: ["content/en/LandingPage/referral.mdx"] },
216
- { route: "/faq", content: ["content/en/FAQPage/faq.mdx"] },
217
- ...SHAPE_INDEPENDENT_ROWS,
218
- { route: "/privacy", content: ["content/en/PrivacyPage/privacy.mdx"] },
219
- { route: "/terms", content: ["content/en/TermsPage/terms.mdx"] },
220
- {
221
- route: "/purchase-policy",
222
- content: ["content/en/PurchasePolicyPage/purchase-policy.mdx"],
223
- },
224
- ...SIGNAL_COLLECTORS_SAMPLE_ROWS,
225
- ];
191
+ export function isOwnTree(name) {
192
+ if (Object.prototype.hasOwnProperty.call(SHAPE_STARTERS, name))
193
+ return true;
194
+ const alias = ALIASES[name];
195
+ return alias?.ownTree !== false;
196
+ }
226
197
  /**
227
- * `simple-service`'s own rows (#6087) — the T1 lawn-care roster of Epic #5778.
228
- * The second declared template that does not reuse `TEMPLATE_BASE_ROSTER`: a
229
- * lawn-care site sells three named services from `/services`, not from a
230
- * pricing table, so `/how-it-works`, the product page and `/pricing` are absent —
231
- * and so are their seeds: `scaffold()` installs a `_base` or overlay seed
232
- * only for a row that names it (#6646 D1), exactly as for `minimalism-site`.
233
- *
234
- * `/meet`, `/meet/[slug]`, `/meet/confirmed` and `/appointments` are absent
235
- * here on purpose: they are the `scheduling` module's own
236
- * `pageContributions`, merged in by `mergeRoster`. Declaring one here would
237
- * make that merge refuse the template outright (§ D8's both-claimants
238
- * refusal).
239
- *
240
- * `/purchase/success` and `/purchase/cancel` ARE declared here, and that is a
241
- * documented divergence from spec § D3. The spec was written at a pin where
242
- * #6084 was expected to add the pair to `MODULE_SLICES.commerce`'s
243
- * `pageContributions`. It did not: the operator's shape-2 disposition
244
- * (#6084 D12) ships the return pair BASE TREE — `CORE_ROUTE_DIRS` carries
245
- * `[locale]/purchase`, and neither `GATEABLE_SURFACES` nor
246
- * `MODULE_SURFACES.commerce` gained an entry — so the `commerce` slice
247
- * contributes no rows at all and nothing else claims these two routes. The
248
- * issue's AC-1 requires the merged roster to carry them; with no module
249
- * claimant, the template's own roster is the only place that can declare
250
- * them, and doing so trips no collision.
251
- *
252
- * `/thanks/[slug]` is declared here for the same reason (#6646 D2): the
253
- * post-purchase thank-you route ships base tree and no module claims it.
254
- *
255
- * `/purchase-policy` is the service-shape purchase policy (#6646 D3) — its
256
- * cancellation and refunds sections replace the three #5947 policy rows.
257
- *
258
- * `/account` carries no `content`: it is a code route (the `(authed)` group),
259
- * like `/sign-in` on the base roster and like the API-backed rows the
260
- * scheduling module contributes. `/services/[slug]` declares its seed
261
- * DIRECTORY (`contentDir`, `content/en/Service`) — the three service pages.
198
+ * Where a starter's GENERATED roster record lives (spec 6892 D1) — the
199
+ * companion to {@link templateTreeDir}. A treeless alias (`ownTree: false`)
200
+ * has no directory of its own to hold one, so its record lives under
201
+ * `site-templates/aliases/` instead, keyed by its name; every other name's
202
+ * record lives beside its tree, as it always has.
262
203
  */
263
- const SIMPLE_SERVICE_ROSTER = [
264
- { route: "/", content: ["content/en/HomePage/home.mdx"] },
265
- { route: "/services", content: ["content/en/ServicesList/services.mdx"] },
266
- { route: "/services/[slug]", contentDir: "content/en/Service" },
267
- { route: "/about", content: ["content/en/AboutPage/about.mdx"] },
268
- { route: "/contact", content: ["content/en/ContactPage/contact.mdx"] },
269
- { route: "/faq", content: ["content/en/FAQPage/faq.mdx"] },
270
- ...SHAPE_INDEPENDENT_ROWS,
271
- {
272
- route: "/purchase-policy",
273
- content: ["content/en/PurchasePolicyPage/purchase-policy.mdx"],
274
- },
275
- { route: "/privacy", content: ["content/en/PrivacyPage/privacy.mdx"] },
276
- { route: "/terms", content: ["content/en/TermsPage/terms.mdx"] },
277
- { route: "/account" },
278
- { route: "/purchase/success" },
279
- { route: "/purchase/cancel" },
280
- { route: "/thanks/[slug]" },
281
- ];
204
+ export function shippedManifestPath(name) {
205
+ const alias = ALIASES[name];
206
+ if (alias?.ownTree === false) {
207
+ return `aliases/${name}.TEMPLATE-MANIFEST.json`;
208
+ }
209
+ return `${templateTreeDir(name)}/TEMPLATE-MANIFEST.json`;
210
+ }
282
211
  /**
283
- * The three settled templates and their fixed `requires` lists — settled by
284
- * Sam, 2026-09-14 (parent § D12). The plain name is the Pro one: it is the
285
- * flagship and the demo.
286
- *
287
- * `workspace` appears in none of them. It is a Pro module, but an authed app
288
- * surface contributing no PUBLIC page to a marketing site, and `requires`
289
- * names what the roster uses (parent § D15). A Pro key unlocks it regardless
290
- * of any template. `ModuleName` could express it either way — `workspace` is
291
- * one of the seven `RECOGNIZED_MODULES` names; it is simply not one any
292
- * template's fixed `requires` list names.
212
+ * Look up a declared template — the one exported function that yields a
213
+ * template's roster (#6753 AC-1); `TEMPLATE_MANIFESTS`, the frozen record it
214
+ * reads, holds only manifests composed from `ALIASES`.
293
215
  *
294
- * `commerce` is likewise in none of them: it is a per-site add-on available at
295
- * any level, so a template for it would contradict the offering model and
296
- * multiply the set combinatorially (parent § D13). It still contributes rows
297
- * to whichever template takes it, through `mergeRoster` below.
216
+ * With no `aliases`, returns the frozen `TEMPLATE_MANIFESTS` entry itself.
217
+ * With an injected alias table — the `resolveTemplateLineage` precedent
218
+ * (#5507) — composes `aliases[name]` against the shipped shape rosters, so a
219
+ * test exercises the composer's refusals without touching the shipped table.
298
220
  *
299
- * Frozen (#5507): this registry is also the issuer's authorization allowlist
300
- * (spec 5480 § C7) — a mutable global there could be monkey-patched at
301
- * runtime. A caller needing a different registry for a test injects its own
302
- * fixture rather than mutating this one.
221
+ * @throws TemplateError on an undeclared name, or a malformed injected alias.
303
222
  */
304
- export const TEMPLATE_MANIFESTS = Object.freeze({
305
- /**
306
- * The Free template, and the mechanism's own proof (parent § D11): it
307
- * requires only `marketing`, so the keyless merge path is exercised by a
308
- * template we ship anyway rather than by a throwaway fixture.
309
- */
310
- "marketing-site-free": {
311
- name: "marketing-site-free",
312
- requires: ["marketing"],
313
- // The demo site carries the "Get started with this template" banner
314
- // (operator ruling 2026-09-23); every other layout knob keeps its default.
315
- layout: { announcementBanner: true },
316
- categories: ["accounting", "legal", "technology"],
317
- shape: "organization-product",
318
- design: "standard",
319
- extends: BASE_LAYER_DIR,
320
- roster: [...TEMPLATE_BASE_ROSTER, FREE_BLOG_TEASER_ROW],
321
- },
322
- /**
323
- * The Pro flagship. Its module list is OFFERINGS § 4 offering 3's tiers line
324
- * — "docs, scheduling and flows modules" — where "flows" is
325
- * `signal-collectors` (`MODULE_SURFACES` maps it to `["flows"]`).
326
- */
327
- "marketing-site": {
328
- name: "marketing-site",
329
- requires: ["marketing", "docs-blog", "scheduling", "signal-collectors"],
330
- // The flagship is the general-purpose template: it carries the whole
331
- // registry, so every group tile on the hub has at least one template.
332
- categories: [
333
- "accounting",
334
- "legal",
335
- "hvac",
336
- "roofing",
337
- "plumbing",
338
- "electrical",
339
- "landscaping",
340
- "auto-repair",
341
- "real-estate",
342
- "technology",
343
- "medical-specialty",
344
- ],
345
- shape: "organization-product",
346
- design: "standard",
347
- extends: BASE_LAYER_DIR,
348
- roster: [...TEMPLATE_BASE_ROSTER, ...SIGNAL_COLLECTORS_SAMPLE_ROWS],
349
- },
350
- /**
351
- * Pro Plus — the Pro list plus `multi-tenant`, which is correctly in the
352
- * list because it DOES contribute pages (the org and workspace surfaces).
353
- * Its `pageContributions` is nonetheless unset today, deferred to its own
354
- * instance, so this template's merged roster carries the same contributed
355
- * rows as `marketing-site`'s and no more. That is a module which has
356
- * declared no rows yet — visible in the diff — not a merge that dropped
357
- * declared rows.
358
- */
359
- "saas-product": {
360
- name: "saas-product",
361
- requires: [
362
- "marketing",
363
- "docs-blog",
364
- "scheduling",
365
- "signal-collectors",
366
- "multi-tenant",
367
- ],
368
- categories: ["technology", "medical-specialty"],
369
- shape: "organization-product",
370
- design: "standard",
371
- extends: BASE_LAYER_DIR,
372
- roster: [...TEMPLATE_BASE_ROSTER, ...SIGNAL_COLLECTORS_SAMPLE_ROWS],
373
- },
374
- /**
375
- * The second Pro template (#5589) — the reports-first, scroll-snap deck
376
- * site. NOT `marketing-site` plus a module: its `requires` omits
377
- * `docs-blog`, so the two Pro templates are siblings under `_base`
378
- * (#5618 D2), never a lineage. Level still derives from `requires`:
379
- * `scheduling` and `signal-collectors` are paid, so `templateDelivery`
380
- * returns `"paid"` exactly as it does for `marketing-site`.
381
- *
382
- * It declares its OWN roster rather than reusing `TEMPLATE_BASE_ROSTER`
383
- * (the only declared template that does): the deck home replaces the
384
- * document-scroll home, and `/how-it-works`, the product page, `/pricing` and
385
- * `/sign-in` are not pages this site has. The roster is what the
386
- * template IS, so an unrostered page has neither a route directory
387
- * (`rosterSurfaces`) nor its `_base`/overlay seed (#6646 D1).
388
- *
389
- * It declares NO `layout` block (#6638): the deck home, mark-only nav and
390
- * centered footer are the `minimalism` design's, supplied by
391
- * `DESIGN_LAYOUTS` from `design:` — the look any shape can wear. The
392
- * roster rows the `organization-service` shape does not derive
393
- * (`/reports`, `/referral`) are recorded with their reasons in this
394
- * template's TEMPLATE-MANIFEST.md § 3a.
395
- *
396
- * `/reports` and `/referral` carry no `GATEABLE_SURFACES` entry and need
397
- * none: both are single-segment `LandingPage` slugs served by the generic
398
- * `[slug]` route (#5469 D2), the same mechanism Free's `/how-it-works`
399
- * uses. `/reports/[slug]` (the reader) is the signal-collectors module's
400
- * own contributed row.
401
- *
402
- * `/services/[slug]` declares its seed DIRECTORY as `contentDir`
403
- * (`content/en/Service`, as `routes-map.ts` records it). The three
404
- * samples ride the template tree unmapped, landing at that directory.
405
- */
406
- "minimalism-site": {
407
- name: "minimalism-site",
408
- requires: ["marketing", "scheduling", "signal-collectors"],
409
- categories: ["real-estate", "technology", "legal"],
410
- shape: "organization-service",
411
- design: "minimalism",
412
- extends: BASE_LAYER_DIR,
413
- roster: MINIMALISM_ROSTER,
414
- },
415
- /**
416
- * The T1 lawn-care template (#6087, Epic #5778) — the first site shape
417
- * that composes `scheduling` and `commerce`: three priced services, a
418
- * booker, a checkout return pair and the three #5947 policy pages.
419
- *
420
- * `requires` lists `marketing` for the registry convention every other
421
- * manifest keeps: it is free (`module-tier.ts`'s `FREE_MODULES`), so it
422
- * cannot change `templateDelivery`'s verdict, and listing it keeps the
423
- * template's own declaration — not `DEFAULT_MODULES` — the authority for
424
- * what a bare `--template simple-service` run installs. The order is
425
- * `RECOGNIZED_MODULES` order, which is also `normalizeModules`' output
426
- * order, so the declared list and the merge order read the same.
427
- *
428
- * `scheduling` and `commerce` are both paid, so `templateDelivery`
429
- * returns `"paid"` from either one alone.
430
- *
431
- * It declares NO `layout` block: the default chrome (full nav, column
432
- * footer, no announcement banner) is the one a lawn-care site wants, and
433
- * the issue's STOP boundary excludes layout knobs the spec does not name.
434
- */
435
- "simple-service": {
436
- name: "simple-service",
437
- requires: ["marketing", "scheduling", "commerce"],
438
- categories: ["landscaping"],
439
- shape: "local-business-service",
440
- design: "standard",
441
- extends: BASE_LAYER_DIR,
442
- roster: SIMPLE_SERVICE_ROSTER,
443
- },
444
- });
445
- /** Look up a declared template. @throws TemplateError on an undeclared name. */
446
- export function resolveTemplate(name) {
447
- const manifest = TEMPLATE_MANIFESTS[name];
448
- if (!manifest) {
449
- throw new TemplateError(`unknown template "${name}". Declared: ${Object.keys(TEMPLATE_MANIFESTS).join(", ")}`);
223
+ export function resolveTemplate(name, aliases) {
224
+ const declared = aliases ?? TEMPLATE_MANIFESTS;
225
+ if (!Object.prototype.hasOwnProperty.call(declared, name)) {
226
+ throw new TemplateError(`unknown template "${name}". Declared: ${Object.keys(declared).join(", ")}`);
450
227
  }
451
- return manifest;
228
+ return aliases
229
+ ? composeManifest(name, aliases[name])
230
+ : TEMPLATE_MANIFESTS[name];
452
231
  }
453
232
  /**
454
233
  * Resolve a template's `extends` chain — `[name, …ancestors, "_base"]` — in
@@ -20,22 +20,24 @@
20
20
  * ("Deliberately imports nothing from `parse-args.ts`: that module imports
21
21
  * THIS one … so a back-import would close a cycle"), and `surfaces.ts` is the
22
22
  * precedent this file follows: the flag's vocabulary lives in a module the
23
- * parser can import because it imports nothing at all.
23
+ * parser can import because it reaches nothing that imports the parser.
24
24
  *
25
- * THE REGISTRY IS STILL `template-manifest.ts` (parent record D2). This file
26
- * declares NAMES; that one declares what a template IS — `requires`, the
27
- * roster, the `installMap`, the free-vs-paid derivation — and it stays the
28
- * single place a template is defined. The two cannot drift: the names here
29
- * are asserted equal to `Object.keys(TEMPLATE_MANIFESTS)` by
30
- * `__tests__/template-scaffold.test.ts`, so adding a template without listing
31
- * it here (or the reverse) fails the suite rather than shipping a flag that
32
- * accepts a name the registry does not know.
25
+ * THE NAMES ARE DERIVED, NOT TYPED (#6753 D1). They are the key set of
26
+ * `ALIASES` in `starter-grid.ts` — the alias table `template-manifest.ts`
27
+ * composes every manifest from — so the flag's vocabulary and the registry
28
+ * cannot drift. `starter-grid.ts` is the one file this module imports, and it
29
+ * imports nothing at runtime (every import there is `import type`), so
30
+ * `parse-args.ts` → here → `starter-grid.ts` still reaches no module that
31
+ * imports `parse-args.ts` back. `ALIASES` does not live in
32
+ * `template-manifest.ts` for exactly that reason: importing it from there
33
+ * would re-open the cycle above.
33
34
  *
34
35
  * Rejected: lazifying `module-tier.ts`'s `PAID_MODULES` to break the cycle at
35
36
  * its source. It is the correct structural fix and it is not this unit's to
36
37
  * make — `module-tier.ts` is a READ row in spec 5481 § 4, and `PAID_MODULES`
37
38
  * is consumed as a plain array across the package.
38
39
  */
39
- /** The declared site templates, in `TEMPLATE_MANIFESTS` declaration order. */
40
- export declare const RECOGNIZED_TEMPLATES: readonly ["marketing-site-free", "marketing-site", "saas-product", "minimalism-site", "simple-service"];
41
- export type TemplateName = (typeof RECOGNIZED_TEMPLATES)[number];
40
+ import { ALIASES } from "./starter-grid.js";
41
+ export type TemplateName = keyof typeof ALIASES;
42
+ /** The declared site templates, in `ALIASES` (and so `TEMPLATE_MANIFESTS`) declaration order. */
43
+ export declare const RECOGNIZED_TEMPLATES: readonly TemplateName[];