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
package/dist/surfaces.js CHANGED
@@ -31,6 +31,7 @@
31
31
  * `MODULE_SURFACES`, and `__tests__/surface-gating.test.ts` asserts those keys
32
32
  * equal `RECOGNIZED_MODULES` so the two cannot drift.
33
33
  */
34
+ import { createHash } from "node:crypto";
34
35
  /** Template-relative prefix every `apps/web` route directory sits under. */
35
36
  const ROUTE_ROOT = "apps/web/app/[locale]";
36
37
  /**
@@ -81,13 +82,6 @@ export const CORE_ROUTE_DIRS = [
81
82
  // scaffold carrying it and no content is byte-for-byte a scaffold with one
82
83
  // more 404, nothing else.
83
84
  `${ROUTE_ROOT}/[slug]`,
84
- // #6658 D3 — the product's own detail page (`/software/product`, a
85
- // SoftwareProduct → SoftwareApplication), which replaced the /features
86
- // stand-in. Core for the same reason [slug] is: its content
87
- // (`content/en/SoftwareProduct/product.mdx`) is in the always-copied overlay
88
- // and the generated nav always links it, so every scaffold — template or
89
- // not — must carry the route.
90
- `${ROUTE_ROOT}/software`,
91
85
  // #6084 D12 (the operator's shape-2 disposition) — the checkout return
92
86
  // pair. The launchers pointing at them (`apps/mcp/src/tools/purchase.ts`,
93
87
  // `apps/web/.../dev/purchase/purchase.actions.ts`) are ALREADY
@@ -121,19 +115,22 @@ export const NON_ROUTE_DIRS = new Set([
121
115
  * byte-deterministic).
122
116
  */
123
117
  export const GATEABLE_SURFACES = [
118
+ {
119
+ // #6878 D9 — the comparison hub/spoke pair (`ComparisonsList` at
120
+ // /alternatives, `ComparisonPage` at /vs/<slug>). No `navLabelKey`: no
121
+ // roster names it by default, so it excludes at scaffold time exactly
122
+ // like any other gateable surface a roster leaves off. `add-page`
123
+ // installs it (D11) the first time a site adds a comparison.
124
+ id: "alternatives",
125
+ routes: [`${ROUTE_ROOT}/alternatives`, `${ROUTE_ROOT}/vs`],
126
+ content: ["content/<locale>/ComparisonsList", "content/<locale>/ComparisonPage"],
127
+ },
124
128
  {
125
129
  id: "blog",
126
130
  routes: [`${ROUTE_ROOT}/blog`],
127
131
  content: ["apps/web/content/<locale>/blog"],
128
132
  navLabelKey: "blog",
129
133
  },
130
- {
131
- // Module-less (#6658 D5), like `pricing`: every starter's roster names
132
- // it. No `navLabelKey` — the footer links it, gated on this surface.
133
- id: "book-a-call",
134
- routes: [`${ROUTE_ROOT}/book-a-call`],
135
- content: ["content/<locale>/BookCall"],
136
- },
137
134
  {
138
135
  id: "careers",
139
136
  routes: [`${ROUTE_ROOT}/careers`],
@@ -180,12 +177,27 @@ export const GATEABLE_SURFACES = [
180
177
  routes: [`${ROUTE_ROOT}/events`],
181
178
  content: ["content/<locale>/EventsList", "content/<locale>/Event"],
182
179
  },
180
+ {
181
+ // #6721 — the product's feature pages: a FeaturesList hub over one
182
+ // Feature page per feature. The Free roster enables it by its rows.
183
+ id: "features",
184
+ routes: [`${ROUTE_ROOT}/features`],
185
+ content: ["content/<locale>/FeaturesList", "content/<locale>/Feature"],
186
+ },
183
187
  {
184
188
  // API-backed (the flow engine); no MDX of its own.
185
189
  id: "flows",
186
190
  routes: [`${ROUTE_ROOT}/flows`],
187
191
  content: [],
188
192
  },
193
+ {
194
+ // #6878 D3 — the persona hub, renamed from `use-cases` (route-naming
195
+ // convention #3450: /for/{persona} spokes under a /for hub). Every shape
196
+ // roster carries it now (D7), service shapes included.
197
+ id: "for",
198
+ routes: [`${ROUTE_ROOT}/for`],
199
+ content: ["content/<locale>/UseCasesList", "content/<locale>/UseCase"],
200
+ },
189
201
  {
190
202
  // Module-less (#6658 D5) — see `book-a-call`.
191
203
  id: "glossary",
@@ -240,6 +252,11 @@ export const GATEABLE_SURFACES = [
240
252
  routes: [`${ROUTE_ROOT}/partners`],
241
253
  content: ["content/<locale>/PartnersList", "content/<locale>/Partner"],
242
254
  },
255
+ {
256
+ id: "portfolio",
257
+ routes: [`${ROUTE_ROOT}/portfolio`],
258
+ content: ["content/<locale>/PortfolioList", "content/<locale>/PortfolioItem"],
259
+ },
243
260
  {
244
261
  // Module-LESS by construction (#5479 D16): no MODULE_SURFACES value names
245
262
  // it, so a site template may enable it by roster alone. Same for
@@ -276,6 +293,13 @@ export const GATEABLE_SURFACES = [
276
293
  routes: [`${ROUTE_ROOT}/services`],
277
294
  content: ["content/<locale>/ServicesList", "content/<locale>/Service"],
278
295
  },
296
+ {
297
+ // #6721 — the product's own page (#6658 D3), a gateable surface again now
298
+ // that the Free roster links /features instead of /software/product.
299
+ id: "software",
300
+ routes: [`${ROUTE_ROOT}/software`],
301
+ content: ["content/<locale>/SoftwareProduct"],
302
+ },
279
303
  {
280
304
  id: "tech-articles",
281
305
  routes: [`${ROUTE_ROOT}/tech-articles`],
@@ -291,11 +315,6 @@ export const GATEABLE_SURFACES = [
291
315
  routes: [`${ROUTE_ROOT}/testimonials`],
292
316
  content: ["content/<locale>/TestimonialsList"],
293
317
  },
294
- {
295
- id: "use-cases",
296
- routes: [`${ROUTE_ROOT}/use-cases`],
297
- content: ["content/<locale>/UseCasesList", "content/<locale>/UseCase"],
298
- },
299
318
  {
300
319
  id: "whitepapers",
301
320
  routes: [`${ROUTE_ROOT}/whitepapers`],
@@ -311,6 +330,9 @@ export const RECOGNIZED_SURFACES = GATEABLE_SURFACES.map((s) => s.id);
311
330
  */
312
331
  export const SURFACE_ALIASES = {
313
332
  schedule: "meet",
333
+ // #6878 D3 — one-release alias for the /use-cases -> /for rename, same
334
+ // removal trigger as `schedule` above.
335
+ "use-cases": "for",
314
336
  };
315
337
  /**
316
338
  * Surface ids that became core routes (#6658 D3: `software` — the product's
@@ -319,7 +341,10 @@ export const SURFACE_ALIASES = {
319
341
  * so a site scaffolded with `--surfaces software` keeps working: its route
320
342
  * is copied regardless.
321
343
  */
322
- export const CORE_PROMOTED_SURFACES = new Set(["software"]);
344
+ // #6721 — `software` is a gateable surface again (the Free roster now carries
345
+ // /features + /features/[slug] instead); the set is kept, empty, so the
346
+ // accept-and-drop read sites stay in place for the next promotion.
347
+ export const CORE_PROMOTED_SURFACES = new Set([]);
323
348
  /** Canonical id for a `--surfaces` value, resolving a retired alias. */
324
349
  function canonicalizeSurface(s) {
325
350
  return SURFACE_ALIASES[s] ?? s;
@@ -492,10 +517,6 @@ export function excludedContentPaths(enabled) {
492
517
  export const ROUTE_SINCE = {
493
518
  about: "0.0.0",
494
519
  "blog/[[...slug]]": "0.0.0",
495
- // #6658 — four genuinely new route directories (D4, D5), attaching at
496
- // today's fixed-group version, the same "attach at today's version" rule
497
- // #6084, #5977, #5678, #5479, #5371 and #5048 applied.
498
- "book-a-call": "0.26.0",
499
520
  careers: "0.0.0",
500
521
  "careers/[slug]": "0.0.0",
501
522
  "case-studies": "0.0.0",
@@ -509,10 +530,17 @@ export const ROUTE_SINCE = {
509
530
  downloads: "0.0.0",
510
531
  events: "0.0.0",
511
532
  "events/[slug]": "0.0.0",
533
+ // #6721 — the feature pages, a new route pair at today's line.
534
+ features: "0.27.0",
535
+ "features/[slug]": "0.27.0",
512
536
  faq: "0.0.0",
513
537
  "flows/[slug]": "0.0.0",
514
538
  "flows/[slug]/results/[payload]": "0.14.0",
515
539
  "flows/[slug]/start": "0.18.0",
540
+ // #6658 — genuinely new route directories (D4, D5), attaching at today's
541
+ // fixed-group version, the same "attach at today's version" rule #6084,
542
+ // #5977, #5678, #5479, #5371 and #5048 applied. `book-a-call` was a third
543
+ // such entry, retired by #6897 D10.
516
544
  glossary: "0.26.0",
517
545
  "how-to": "0.0.0",
518
546
  "how-to/[slug]": "0.0.0",
@@ -531,6 +559,9 @@ export const ROUTE_SINCE = {
531
559
  // #5479 — three genuinely new route directories, so they attach at today's
532
560
  // fixed-group version rather than the "predates the manifest" 0.0.0 above,
533
561
  // the same rule #5048 ("projects/apply") and #5014 ("(authed)") applied.
562
+ // #6687 — the before/after portfolio, a new route pair at today's line.
563
+ portfolio: "0.27.0",
564
+ "portfolio/[slug]": "0.27.0",
534
565
  pricing: "0.22.0",
535
566
  privacy: "0.22.0",
536
567
  products: "0.0.0",
@@ -558,8 +589,14 @@ export const ROUTE_SINCE = {
558
589
  terms: "0.22.0",
559
590
  testimonials: "0.0.0",
560
591
  "thanks/[slug]": "0.14.0",
561
- "use-cases": "0.0.0",
562
- "use-cases/[slug]": "0.26.0",
592
+ // #6878 — the persona family, renamed from `use-cases`/`use-cases/[slug]`.
593
+ // Attaches at today's fixed-group version, the same "attach at today's
594
+ // version" rule #6084, #5977, #5678, #5479, #5371 and #5048 applied.
595
+ for: "0.28.0",
596
+ "for/[slug]": "0.28.0",
597
+ // #6878 D9 — the comparison hub/spoke pair.
598
+ alternatives: "0.28.0",
599
+ "vs/[slug]": "0.28.0",
563
600
  whitepapers: "0.0.0",
564
601
  "whitepapers/[slug]": "0.0.0",
565
602
  // #5014 — the (authed) route GROUP: layout.tsx + sign-in-gate.tsx sit
@@ -624,6 +661,10 @@ export const ROUTE_SINCE = {
624
661
  // directory, so it attaches at today's fixed-group version, the same rule
625
662
  // #5479, #5371 and #5048 applied.
626
663
  "[slug]": "0.23.0",
664
+ // #6897 D2 — the Free /meet/<slug> placeholder detail route. A genuinely
665
+ // new route directory, so it attaches at today's fixed-group version, the
666
+ // same rule applied above.
667
+ "[slug]/[detail]": "0.28.0",
627
668
  };
628
669
  /**
629
670
  * Surface id of the registry that owns `routeDir` (ROUTE_ROOT-relative): the
@@ -659,12 +700,18 @@ export function surfaceForRouteDir(routeDir) {
659
700
  */
660
701
  export const ROUTE_DEPENDENCIES = {
661
702
  // #5963 — the (auth) group's layout renders the brand mark through
662
- // `siteLogo()`, and the LOCALE layout above it branches on
663
- // `isAuthPath()` to drop the marketing chrome for these five paths: without
664
- // `auth-routes.ts` a synced group renders inside the Nav + Footer it exists
665
- // to replace. The sign-in page no longer imports `site-brand` — the mark is
666
- // the shell's since #5963 — so its own list is just the env reader.
667
- "(auth)": ["apps/web/lib/site-brand.tsx", "apps/web/lib/auth-routes.ts"],
703
+ // `siteLogo()` — which lives in `site-logo.tsx` since #6820 and reads the
704
+ // raw mark + name from `site-brand.tsx`, so BOTH files travel with the
705
+ // group — and the LOCALE layout above it branches on `isAuthPath()` to drop
706
+ // the marketing chrome for these five paths: without `auth-routes.ts` a
707
+ // synced group renders inside the Nav + Footer it exists to replace. The
708
+ // sign-in page no longer imports `site-brand` — the mark is the shell's
709
+ // since #5963 — so its own list is just the env reader.
710
+ "(auth)": [
711
+ "apps/web/lib/site-brand.tsx",
712
+ "apps/web/lib/site-logo.tsx",
713
+ "apps/web/lib/auth-routes.ts",
714
+ ],
668
715
  "(auth)/sign-in": ["apps/web/lib/auth-env.ts"],
669
716
  "(auth)/sign-up": ["apps/web/lib/auth-env.ts"],
670
717
  "(auth)/forgot-password": ["apps/web/lib/auth-env.ts"],
@@ -739,6 +786,46 @@ export const ROUTE_MESSAGE_NAMESPACES = {
739
786
  };
740
787
  /** Template-relative path of the generated site-nav registry. */
741
788
  export const NAV_CONFIG_PATH = "apps/web/lib/nav-config.ts";
789
+ /**
790
+ * `renderNavConfig`'s first-line stamp (#6908 D1): `// ailk:generated
791
+ * sha256=<64 lowercase hex>`, recognised whether it ends in `\n` or `\r\n`.
792
+ * `<hex>` is the SHA-256 of every byte after the stamp line's terminator,
793
+ * CRLF folded to LF first — the same fold-and-hash rule `claude-md.ts`
794
+ * `hashBody` uses for the scaffold's `CLAUDE.md` regions (#6904), given its
795
+ * own definition here (N1) rather than an import, since the two files hash
796
+ * unrelated bodies.
797
+ */
798
+ const NAV_STAMP_RE = /^\/\/ ailk:generated sha256=([0-9a-f]{64})\r?\n/;
799
+ /** SHA-256, lowercase hex, of a nav body with every CRLF folded to LF (D1, N1). */
800
+ function hashNavBody(body) {
801
+ return createHash("sha256")
802
+ .update(body.replace(/\r\n/g, "\n"), "utf8")
803
+ .digest("hex");
804
+ }
805
+ /**
806
+ * Verify a rendered nav's self-hashing stamp (#6908 D1/F1–F2): `"valid"` when
807
+ * the first line's hash matches every byte after it, `"invalid"` when a
808
+ * stamp line is present but the hash fails (an edited, still-stamped nav),
809
+ * `"absent"` when there is no stamp line at all. A stamp that verifies proves
810
+ * the file unedited whatever generator wrote it — the check needs only the
811
+ * file, never the generator's code.
812
+ */
813
+ export function readNavConfigStamp(text) {
814
+ const match = NAV_STAMP_RE.exec(text);
815
+ if (match === null)
816
+ return "absent";
817
+ const body = text.slice(match[0].length);
818
+ return match[1] === hashNavBody(body) ? "valid" : "invalid";
819
+ }
820
+ /**
821
+ * Every byte after a nav's stamp line, or `text` unchanged when it carries
822
+ * none. The one place the D1 stamp-line shape is matched outside
823
+ * `readNavConfigStamp` itself, so a test simulating an edit below the stamp
824
+ * (or a bare pre-D1 body) never re-derives `NAV_STAMP_RE`'s pattern by hand.
825
+ */
826
+ export function stripNavConfigStamp(text) {
827
+ return text.replace(NAV_STAMP_RE, "");
828
+ }
742
829
  /**
743
830
  * Render `apps/web/lib/nav-config.ts` for an enabled surface set.
744
831
  *
@@ -749,14 +836,19 @@ export const NAV_CONFIG_PATH = "apps/web/lib/nav-config.ts";
749
836
  * surface set for the same reason the route tree does.
750
837
  *
751
838
  * The base items are the Free marketing-site chrome (#5469 D5, the approved
752
- * chrome diff recorded on that card, re-pointed by #6658 D8):
753
- * a Product flyout (Use cases · Features · FAQ), then Use cases, Blog, Pricing
754
- * and About. Features links the product's own page (`/software/product`), a
755
- * core route like `/faq` and `/about`; `/blog` is always linked (the teaser or
756
- * the docs-blog module serves it). Use cases and Pricing are gateable
757
- * surfaces: each item is emitted only when its surface is enabled (a template
758
- * roster enables it, #5469 D10), so the nav still never links a page the
759
- * scaffold does not carry.
839
+ * chrome diff recorded on that card, re-pointed by #6658 D8, then by #6878 D6):
840
+ * ONE flyout rule for every shape — an offering column (Features on product
841
+ * shapes, Services on service shapes) beside a Customers column linking the
842
+ * /for persona pages — then Blog, Pricing and About. `/blog` is always linked
843
+ * (the teaser or the docs-blog module serves it). Pricing and every other
844
+ * gateable surface: each item is emitted only when its surface is enabled (a
845
+ * template roster enables it, #5469 D10), so the nav still never links a page
846
+ * the scaffold does not carry. Portfolio is never a flyout link (D6): on a
847
+ * service shape it is a plain top-level link directly after the Services
848
+ * flyout, and every shape's footer Site pages column links it (#6895), both
849
+ * under the `portfolio` surface's gate. No empty flyout is ever emitted: with
850
+ * neither column non-empty, no flyout item is emitted at all (closes #6766's
851
+ * AC-1).
760
852
  *
761
853
  * Only gateable surfaces carrying a `navLabelKey` are linked after the base
762
854
  * items, because a nav entry needs a translated label. Emitting an entry whose
@@ -770,78 +862,155 @@ export const NAV_CONFIG_PATH = "apps/web/lib/nav-config.ts";
770
862
  * Deterministic: gateable entries follow GATEABLE_SURFACES order (alphabetical
771
863
  * by id), after the fixed base items.
772
864
  */
773
- export function renderNavConfig(enabled) {
865
+ export function renderNavConfig(enabled, options) {
774
866
  const on = new Set(enabled);
775
867
  // `blog` is linked unconditionally below (operator ruling 2026-09-23), so
776
- // it is excluded here; `use-cases` is the Free roster's own page (a
777
- // module-less surface the roster enables, like `pricing`), linked in its
778
- // fixed positions below rather than appended.
779
- const linked = GATEABLE_SURFACES.filter((s) => s.navLabelKey !== undefined &&
780
- on.has(s.id) &&
781
- s.id !== "blog" &&
782
- s.id !== "use-cases");
783
- const useCases = on.has("use-cases");
868
+ // it is excluded here; `for` is the Customers flyout/footer column below,
869
+ // linked in its own fixed positions rather than appended.
870
+ const linked = GATEABLE_SURFACES.filter((s) => s.navLabelKey !== undefined && on.has(s.id) && s.id !== "blog");
871
+ const features = on.has("features");
872
+ const services = on.has("services");
873
+ const forEnabled = on.has("for");
874
+ const portfolio = on.has("portfolio");
875
+ const serviceSlugs = [...(options?.serviceSlugs ?? [])].sort().slice(0, 3);
876
+ // #6878 D6 (operator ruling 2026-09-27): one flyout rule for every shape.
877
+ // The `groups` render the desktop panel; `features` carries the same links
878
+ // for the mobile disclosure and the active-path match.
879
+ const featureLinks = features
880
+ ? [
881
+ ' { labelKey: "feature1", path: "/features/feature-1" },',
882
+ ' { labelKey: "feature2", path: "/features/feature-2" },',
883
+ ' { labelKey: "feature3", path: "/features/feature-3" },',
884
+ ]
885
+ : [];
886
+ const serviceLinks = !features && services
887
+ ? serviceSlugs.map((slug, i) => ` { labelKey: "service${i + 1}", path: "/services/${slug}" },`)
888
+ : [];
889
+ const customerLinks = forEnabled
890
+ ? [
891
+ ' { labelKey: "customer1", path: "/for/persona-1" },',
892
+ ' { labelKey: "customer2", path: "/for/persona-2" },',
893
+ ' { labelKey: "customer3", path: "/for/persona-3" },',
894
+ ]
895
+ : [];
896
+ const indent = (line) => ` ${line}`;
897
+ const group = (headingKey, links) => links.length === 0
898
+ ? []
899
+ : [
900
+ " {",
901
+ ` headingKey: "${headingKey}",`,
902
+ " items: [",
903
+ ...links.map(indent),
904
+ " ],",
905
+ " },",
906
+ ];
907
+ // Offering column: Features on a product shape, Services on a service
908
+ // shape. `features` wins when both surfaces are somehow on at once — the
909
+ // Free roster never enables both.
910
+ const offeringLinks = features ? featureLinks : serviceLinks;
911
+ const offeringLabelKey = features ? "product" : "services";
912
+ const offeringHeadingKey = features
913
+ ? "flyoutFeaturesHeading"
914
+ : "flyoutServicesHeading";
915
+ const flyoutGroups = [
916
+ ...group(offeringHeadingKey, offeringLinks),
917
+ ...group("flyoutCustomersHeading", customerLinks),
918
+ ];
919
+ // #6942 — the Services flyout's action bar links the /services hub. The
920
+ // hub rides the `services` surface's gate: its route directory serves both
921
+ // /services and /services/[slug], so every roster that enables the surface
922
+ // carries the hub. Emitted only inside a flyout that has a column (never
923
+ // an action-only flyout). A flyout action requires an icon
924
+ // (`NavFlyoutAction`), imported below only when this link is emitted.
925
+ const servicesHub = !features && services && flyoutGroups.length > 0;
784
926
  const entries = [
785
- " {",
786
- ' labelKey: "product",',
787
- " flyout: {",
788
- // SOFT_LOCK_OK: #6508 — width "panel" (D4/D5): a fixed 640px, two-column
789
- // grid, replacing the "list"/"trigger" one-column menu. `presentation` is
790
- // omitted — it defaults to "grid" (D5). The `docs` action is dropped (it
791
- // duplicated the first feature's /how-it-works destination, § 1.4).
792
- ' width: "panel",',
793
- " features: [",
794
- ...(useCases
795
- ? [' { labelKey: "useCases", descKey: "useCasesDesc", icon: Layers, path: "/use-cases" },']
927
+ // No empty flyout (D6): emitted only when at least one column has links.
928
+ ...(flyoutGroups.length === 0
929
+ ? []
930
+ : [
931
+ " {",
932
+ ` labelKey: "${offeringLabelKey}",`,
933
+ " flyout: {",
934
+ // SOFT_LOCK_OK: #6508 — width "panel" (D4/D5): a fixed 640px panel.
935
+ ' width: "panel",',
936
+ " features: [",
937
+ ...offeringLinks,
938
+ ...customerLinks,
939
+ " ],",
940
+ " groups: [",
941
+ ...flyoutGroups,
942
+ " ],",
943
+ servicesHub
944
+ ? ' actions: [{ labelKey: "services", icon: ArrowRight, path: "/services" }],'
945
+ : " actions: [],",
946
+ " },",
947
+ " },",
948
+ ]),
949
+ // #6895 (operator decision 2026-09-27): a service shape's header reads
950
+ // Services ▾ · Portfolio · Blog · About — Portfolio a plain top-level
951
+ // link, never a flyout link (D6), and the first item when no flyout is
952
+ // emitted. Product shapes keep their header.
953
+ ...(!features && services && portfolio
954
+ ? [' { labelKey: "portfolio", path: "/portfolio" },']
796
955
  : []),
797
- ' { labelKey: "features", descKey: "featuresDesc", icon: Sparkles, path: "/software/product" },',
798
- ' { labelKey: "faq", descKey: "faqDesc", icon: HelpCircle, path: "/faq" },',
799
- " ],",
800
- " actions: [],",
801
- " },",
802
- " },",
803
- // Operator ruling 2026-09-23: Product · Use cases · Blog · Pricing · About.
956
+ // Operator ruling 2026-09-25: [flyout ·] Blog · Pricing · About.
804
957
  // `/blog` is always linked — the `blog` surface serves the real route, and
805
958
  // with it off the `[slug]` route serves the overlay's module page.
806
- ...(useCases ? [' { labelKey: "useCases", path: "/use-cases" },'] : []),
807
959
  ' { labelKey: "blog", path: "/blog" },',
808
960
  ...(on.has("pricing") ? [' { labelKey: "pricing", path: "/pricing" },'] : []),
809
961
  ' { labelKey: "about", path: "/about" },',
810
962
  ...linked.map((s) => ` { labelKey: "${s.navLabelKey}", path: "/${s.id}" },`),
811
963
  ];
812
- // The footer's column groups (operator ruling 2026-09-23, replacing #6468's
813
- // Product/Company pair): Site pages, Features, Resources. Pricing rides the
964
+ // The footer's column groups (operator rulings 2026-09-23 + 2026-09-25,
965
+ // #6878 D6). Site pages, Features, Customers, Resources. Pricing rides the
814
966
  // same `pricing` gate as the header item; `/blog` is always linked (above).
815
- // The product links point at the product's own page, `/software/product`
816
- // (#6658 D3): `software` is core, so they stay unconditional. Book a call,
817
- // Glossary and Newsletter (#6658 D8) each ride their own surface's gate.
967
+ // The Features column links the three feature pages and the /features hub;
968
+ // the Customers column the three personas and the /for hub — each rides its
969
+ // surface's gate. Glossary and Newsletter (#6658 D8) each ride their own
970
+ // surface's gate. Book a call is always linked (#6897 D9), the same /blog
971
+ // precedent above: it retargets to /meet/book-a-call, served as a Free
972
+ // placeholder or the live scheduling page depending on what the site
973
+ // carries. No other footer column changes (D6) — there is deliberately no
974
+ // footer Services column.
818
975
  const footerEntries = [
819
976
  " {",
820
977
  ' headingKey: "footerSitePagesHeading",',
821
978
  " items: [",
822
- ' { labelKey: "features", path: "/software/product" },',
979
+ ...(features ? [' { labelKey: "features", path: "/features" },'] : []),
823
980
  ...(on.has("pricing")
824
981
  ? [' { labelKey: "pricing", path: "/pricing" },']
825
982
  : []),
826
- ...(useCases ? [' { labelKey: "useCases", path: "/use-cases" },'] : []),
983
+ // #6895 — every shape's Site pages column links Portfolio, gated.
984
+ ...(portfolio
985
+ ? [' { labelKey: "portfolio", path: "/portfolio" },']
986
+ : []),
827
987
  ' { labelKey: "about", path: "/about" },',
828
988
  ' { labelKey: "contact", path: "/contact" },',
829
- ...(on.has("book-a-call")
830
- ? [' { labelKey: "bookACall", path: "/book-a-call" },']
831
- : []),
832
- " ],",
833
- " },",
834
- " {",
835
- ' headingKey: "footerFeaturesHeading",',
836
- " items: [",
837
- ' { labelKey: "feature1", path: "/software/product" },',
838
- ' { labelKey: "feature2", path: "/software/product" },',
839
- ' { labelKey: "feature3", path: "/software/product" },',
840
- ' { labelKey: "feature4", path: "/software/product" },',
841
- ' { labelKey: "feature5", path: "/software/product" },',
842
- ' { labelKey: "allFeatures", path: "/software/product" },',
989
+ ' { labelKey: "bookACall", path: "/meet/book-a-call" },',
843
990
  " ],",
844
991
  " },",
992
+ ...(features
993
+ ? [
994
+ " {",
995
+ ' headingKey: "footerFeaturesHeading",',
996
+ " items: [",
997
+ ...featureLinks.map((l) => l.slice(2)),
998
+ ' { labelKey: "allFeatures", path: "/features" },',
999
+ " ],",
1000
+ " },",
1001
+ ]
1002
+ : []),
1003
+ ...(forEnabled
1004
+ ? [
1005
+ " {",
1006
+ ' headingKey: "footerCustomersHeading",',
1007
+ " items: [",
1008
+ ...customerLinks.map((l) => l.slice(2)),
1009
+ ' { labelKey: "allCustomers", path: "/for" },',
1010
+ " ],",
1011
+ " },",
1012
+ ]
1013
+ : []),
845
1014
  " {",
846
1015
  ' headingKey: "footerResourcesHeading",',
847
1016
  " items: [",
@@ -867,19 +1036,23 @@ export function renderNavConfig(enabled) {
867
1036
  "from ",
868
1037
  '"@working-theory/ui";',
869
1038
  ].join("");
870
- return `// nav-config.ts — site-nav registry.
1039
+ const body = `// nav-config.ts — site-nav registry.
871
1040
  //
872
1041
  // GENERATED by create-ailk from the declared route surfaces. Every entry here
873
1042
  // points at a route this site actually carries — the nav is regenerated with
874
1043
  // the surface set so it can never link a page that was not scaffolded.
875
1044
  //
1045
+ // The first line hashes the rest of this file: while it matches, \`create-ailk
1046
+ // add-module\` regenerates this nav when a module adds pages; once you edit
1047
+ // below it, the nav is yours and add-module reports the change it wanted
1048
+ // instead.
1049
+ //
876
1050
  // Adding a link: scaffold the page (the \`add-page\` skill), then add an
877
1051
  // entry below with a \`labelKey\` that exists in the \`nav\` namespace of
878
1052
  // \`apps/web/messages/<locale>.json\`. Paths are locale-agnostic — Nav prefixes
879
1053
  // them with the active locale at render.
880
1054
  ${navItemImport}
881
- import { HelpCircle, Layers, Sparkles } from "lucide-react";
882
-
1055
+ ${servicesHub ? 'import { ArrowRight } from "lucide-react";\n' : ""}
883
1056
  export const navItems: NavItem[] = [
884
1057
  ${entries.join("\n")}
885
1058
  ];
@@ -895,4 +1068,7 @@ export const footerColumns: FooterColumnConfig[] = [
895
1068
  ${footerEntries.join("\n")}
896
1069
  ];
897
1070
  `;
1071
+ // D1 — a first-line stamp holding the SHA-256 of the rest of the file
1072
+ // (`body`), so an unedited nav proves itself whatever generator wrote it.
1073
+ return `// ailk:generated sha256=${hashNavBody(body)}\n${body}`;
898
1074
  }