create-ailk 0.6.0 → 0.7.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 (248) hide show
  1. package/README.md +44 -5
  2. package/component-catalog.json +146 -21
  3. package/component-catalog.md +9 -6
  4. package/dist/add-module-core.d.ts +22 -0
  5. package/dist/add-module-core.js +50 -0
  6. package/dist/add-module.d.ts +89 -0
  7. package/dist/add-module.js +334 -0
  8. package/dist/category-profile.d.ts +126 -0
  9. package/dist/category-profile.js +262 -0
  10. package/dist/cli.js +124 -13
  11. package/dist/copy.d.ts +14 -0
  12. package/dist/copy.js +38 -1
  13. package/dist/index.d.ts +4 -2
  14. package/dist/index.js +6 -1
  15. package/dist/lib/extract-module-bundle.d.ts +76 -2
  16. package/dist/lib/extract-module-bundle.js +219 -8
  17. package/dist/lib/license-key-files.d.ts +15 -0
  18. package/dist/lib/license-key-files.js +102 -0
  19. package/dist/lib/module-license-gate.d.ts +6 -1
  20. package/dist/lib/module-license-gate.js +6 -1
  21. package/dist/lib/pro-callout.d.ts +16 -10
  22. package/dist/lib/pro-callout.js +27 -4
  23. package/dist/license-set.d.ts +34 -0
  24. package/dist/license-set.js +194 -0
  25. package/dist/module-architecture.d.ts +20 -0
  26. package/dist/module-architecture.js +77 -19
  27. package/dist/modules/commerce/0.6.0.tar.gz +0 -0
  28. package/dist/modules/commerce/0.6.1.tar.gz +0 -0
  29. package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
  30. package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
  31. package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
  32. package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
  33. package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
  34. package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
  35. package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
  36. package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
  37. package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
  38. package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
  39. package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
  40. package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
  41. package/dist/modules/workspace/0.6.0.tar.gz +0 -0
  42. package/dist/modules/workspace/0.6.1.tar.gz +0 -0
  43. package/dist/parse-args.d.ts +7 -0
  44. package/dist/parse-args.js +23 -1
  45. package/dist/programmatic.d.ts +46 -0
  46. package/dist/programmatic.js +144 -19
  47. package/dist/site-chrome.d.ts +18 -7
  48. package/dist/site-chrome.js +32 -14
  49. package/dist/site-roster.d.ts +127 -0
  50. package/dist/site-roster.js +494 -0
  51. package/dist/surfaces.d.ts +20 -7
  52. package/dist/surfaces.js +149 -28
  53. package/dist/sync-routes.d.ts +9 -0
  54. package/dist/sync-routes.js +2 -2
  55. package/dist/template-manifest.d.ts +58 -2
  56. package/dist/template-manifest.js +196 -69
  57. package/package.json +6 -4
  58. package/site-templates/_base/content/en/BookCall/book-call.mdx +53 -0
  59. package/site-templates/_base/content/en/GlossaryList/glossary.mdx +66 -0
  60. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +68 -0
  61. package/site-templates/_base/content/en/Pricing/pricing.mdx +71 -62
  62. package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +2 -3
  63. package/site-templates/_base/content/en/TermsPage/terms.mdx +2 -3
  64. package/site-templates/_base/content/en/UseCase/first-use-case.mdx +23 -0
  65. package/site-templates/_base/content/en/UseCase/second-use-case.mdx +23 -0
  66. package/site-templates/_base/content/en/UseCase/third-use-case.mdx +23 -0
  67. package/site-templates/designs/minimalism/DESIGN.md +162 -0
  68. package/site-templates/designs/standard/DESIGN.md +162 -0
  69. package/site-templates/marketing-site/DESIGN.md +162 -0
  70. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +312 -57
  71. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +209 -90
  72. package/site-templates/marketing-site/ailk-template.json +1 -0
  73. package/site-templates/marketing-site-free/DESIGN.md +162 -0
  74. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +260 -66
  75. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +294 -114
  76. package/site-templates/marketing-site-free/ailk-template.json +3 -2
  77. package/site-templates/minimalism-site/DESIGN.md +162 -0
  78. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +246 -19
  79. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +185 -51
  80. package/site-templates/minimalism-site/ailk-template.json +3 -2
  81. package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +53 -0
  82. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +3 -37
  83. package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
  84. package/site-templates/saas-product/DESIGN.md +162 -0
  85. package/site-templates/simple-service/DESIGN.md +162 -0
  86. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +135 -26
  87. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +114 -59
  88. package/site-templates/simple-service/ailk-template.json +4 -3
  89. package/site-templates/simple-service/content/en/BookCall/book-call.mdx +53 -0
  90. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +1 -36
  91. package/site-templates/simple-service/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
  92. package/templates/.claude/settings.json +15 -0
  93. package/templates/.claude/skills/README.md +8 -8
  94. package/templates/.claude/skills/{scaffold-auth → add-auth}/SKILL.md +7 -7
  95. package/templates/.claude/skills/{scaffold-auth → add-auth}/references/env-vars.md +2 -2
  96. package/templates/.claude/skills/{scaffold-auth → add-auth}/references/oauth-app-setup.md +3 -3
  97. package/templates/.claude/skills/{scaffold-page → add-page}/SKILL.md +76 -62
  98. package/templates/.claude/skills/add-page/references/page-types.md +111 -0
  99. package/templates/.claude/skills/{scaffold-page → add-page}/templates/content.mdx.tmpl +4 -4
  100. package/templates/.claude/skills/{scaffold-page → add-page}/templates/route.tsx.tmpl +1 -1
  101. package/templates/.claude/skills/{audit-site → check-site}/SKILL.md +16 -17
  102. package/templates/.claude/skills/{audit-site → check-site}/references/common-errors.md +3 -3
  103. package/templates/.claude/skills/{audit-site → check-site}/references/findings-glossary.md +16 -14
  104. package/templates/.claude/skills/{audit-site → check-site}/references/severity-levels.md +1 -1
  105. package/templates/.claude/skills/fill-page/SKILL.md +180 -0
  106. package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +80 -0
  107. package/templates/.claude/skills/fill-page/references/section-sources.md +41 -0
  108. package/templates/.claude/skills/{provision-config → setup-deploy}/SKILL.md +8 -8
  109. package/templates/.claude/skills/{provision-config → setup-deploy}/references/env-vars-template.md +2 -2
  110. package/templates/.claude/skills/{provision-config → setup-deploy}/references/runbook-template.md +8 -8
  111. package/templates/.claude/skills/{provision-config → setup-deploy}/references/vercel-json-template.md +1 -1
  112. package/templates/.claude/skills/upgrade-plan/SKILL.md +117 -0
  113. package/templates/.claude/skills/upgrade-plan/references/options.md +24 -0
  114. package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +527 -0
  115. package/templates/.github/workflows/ci.yml +5 -0
  116. package/templates/CLAUDE.md +10 -5
  117. package/templates/DESIGN.md +162 -0
  118. package/templates/README.md +134 -38
  119. package/templates/apps/api/.env.example +2 -1
  120. package/templates/apps/api/package.json +1 -1
  121. package/templates/apps/api/src/config/__tests__/index.test.ts +37 -2
  122. package/templates/apps/api/src/config/index.ts +19 -6
  123. package/templates/apps/api/src/lib/license-key-store.ts +45 -0
  124. package/templates/apps/api/src/lib/site.ts +5 -3
  125. package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +97 -0
  126. package/templates/apps/api/src/middleware/auth.ts +9 -0
  127. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +127 -5
  128. package/templates/apps/api/src/routes/content/get-page.ts +86 -2
  129. package/templates/apps/api/src/server.ts +6 -0
  130. package/templates/apps/mcp/__tests__/audit-single-page.test.ts +75 -1
  131. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +57 -9
  132. package/templates/apps/mcp/package.json +2 -1
  133. package/templates/apps/mcp/src/index.ts +7 -0
  134. package/templates/apps/mcp/src/lib/audit-single-page.ts +36 -10
  135. package/templates/apps/mcp/src/lib/license-key-store.ts +45 -0
  136. package/templates/apps/web/PRODUCT.md +86 -0
  137. package/templates/apps/web/__tests__/setup.ts +18 -0
  138. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +10 -2
  139. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +2 -2
  140. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +10 -4
  141. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +22 -14
  142. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +26 -18
  143. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +12 -4
  144. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +35 -4
  145. package/templates/apps/web/app/[locale]/[slug]/page.tsx +49 -9
  146. package/templates/apps/web/app/[locale]/book-a-call/page.tsx +50 -0
  147. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
  148. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
  149. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +3 -4
  150. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +94 -33
  151. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +53 -23
  152. package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +96 -0
  153. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +84 -2
  154. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +5 -6
  155. package/templates/apps/web/app/[locale]/contact/page.tsx +25 -7
  156. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
  157. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +3 -4
  158. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +4 -5
  159. package/templates/apps/web/app/[locale]/glossary/page.tsx +50 -0
  160. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  161. package/templates/apps/web/app/[locale]/how-to/page.tsx +25 -15
  162. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
  163. package/templates/apps/web/app/[locale]/layout.tsx +113 -51
  164. package/templates/apps/web/app/[locale]/newsletter/page.tsx +50 -0
  165. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
  166. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
  167. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -5
  168. package/templates/apps/web/app/[locale]/{shipping → purchase-policy}/page.tsx +15 -10
  169. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +4 -5
  170. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
  171. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +14 -1
  172. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  173. package/templates/apps/web/app/[locale]/software/page.tsx +25 -15
  174. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  175. package/templates/apps/web/app/[locale]/tech-articles/page.tsx +25 -15
  176. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +3 -4
  177. package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +48 -0
  178. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +3 -4
  179. package/templates/apps/web/app/[locale]/use-cases/page.tsx +15 -2
  180. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  181. package/templates/apps/web/app/[locale]/whitepapers/page.tsx +25 -15
  182. package/templates/apps/web/app/error-fallback.tsx +17 -45
  183. package/templates/apps/web/app/not-found.tsx +14 -38
  184. package/templates/apps/web/app/og/route.tsx +6 -0
  185. package/templates/apps/web/eslint.config.mjs +3 -0
  186. package/templates/apps/web/instrumentation.ts +14 -0
  187. package/templates/apps/web/lib/__tests__/site-theme.test.ts +135 -20
  188. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +38 -0
  189. package/templates/apps/web/lib/license-key-store.ts +52 -0
  190. package/templates/apps/web/lib/nav-config.ts +64 -12
  191. package/templates/apps/web/lib/prisma-for-auth.ts +9 -6
  192. package/templates/apps/web/lib/site-brand.tsx +4 -8
  193. package/templates/apps/web/lib/site-chrome.ts +3 -3
  194. package/templates/apps/web/lib/site-identity.ts +134 -0
  195. package/templates/apps/web/lib/site-theme.ts +11 -0
  196. package/templates/apps/web/messages/en.json +52 -37
  197. package/templates/apps/web/middleware.ts +1 -1
  198. package/templates/apps/web/public/android-chrome-192x192.png +0 -0
  199. package/templates/apps/web/public/android-chrome-512x512.png +0 -0
  200. package/templates/apps/web/public/apple-touch-icon.png +0 -0
  201. package/templates/apps/web/public/favicon-16x16.png +0 -0
  202. package/templates/apps/web/public/favicon-32x32.png +0 -0
  203. package/templates/apps/web/public/favicon.ico +0 -0
  204. package/templates/apps/web/public/favicon.svg +3 -3
  205. package/templates/apps/web/public/logomark.svg +6 -3
  206. package/templates/apps/web/public/logos/ai-launch-kit-light.svg +4 -0
  207. package/templates/apps/web/public/logos/ai-launch-kit.svg +4 -0
  208. package/templates/apps/web/public/logos/atomic-agent-light.svg +4 -0
  209. package/templates/apps/web/public/logos/atomic-agent.svg +4 -0
  210. package/templates/apps/web/public/logos/salessmyth-light.svg +4 -0
  211. package/templates/apps/web/public/logos/salessmyth.svg +4 -0
  212. package/templates/apps/web/public/site.webmanifest +4 -4
  213. package/templates/apps/web/public/wordmark-light.svg +4 -0
  214. package/templates/apps/web/public/wordmark.svg +3 -3
  215. package/templates/apps/web/security-headers.config.ts +5 -1
  216. package/templates/apps/web/src/lib/commerce-sections.ts +35 -3
  217. package/templates/content/_site.mdx +41 -3
  218. package/templates/content/en/AboutPage/about.mdx +150 -144
  219. package/templates/content/en/BlogList/blog.mdx +105 -0
  220. package/templates/content/en/ContactPage/contact.mdx +44 -85
  221. package/templates/content/en/FAQPage/faq.mdx +86 -57
  222. package/templates/content/en/HomePage/home.mdx +276 -217
  223. package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +182 -0
  224. package/templates/content/en/SoftwareProduct/product.mdx +175 -0
  225. package/templates/content/en/UseCasesList/use-cases.mdx +170 -0
  226. package/templates/database/CHANGELOG.md +14 -0
  227. package/templates/database/package.json +3 -2
  228. package/templates/package.json +3 -1
  229. package/templates/tools/lint-architecture.ts +315 -0
  230. package/templates/tools/tsconfig.json +14 -0
  231. package/versions.json +33 -33
  232. package/templates/.claude/skills/add-schema/SKILL.md +0 -277
  233. package/templates/.claude/skills/add-schema/references/conflict-handling.md +0 -200
  234. package/templates/.claude/skills/add-schema/references/file-finding.md +0 -123
  235. package/templates/.claude/skills/add-schema/references/schema-types.md +0 -234
  236. package/templates/.claude/skills/scaffold-page/references/page-types.md +0 -169
  237. package/templates/.claude/skills/suggest-site-pages/SKILL.md +0 -242
  238. package/templates/.claude/skills/suggest-site-pages/references/categories.md +0 -287
  239. package/templates/apps/web/app/[locale]/cancellation/page.tsx +0 -44
  240. package/templates/apps/web/app/[locale]/returns/page.tsx +0 -44
  241. package/templates/apps/web/public/logo-dark.svg +0 -21
  242. package/templates/apps/web/public/logo-light.svg +0 -21
  243. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +0 -108
  244. package/templates/content/en/LandingPage/blog.mdx +0 -83
  245. package/templates/content/en/LandingPage/features.mdx +0 -103
  246. package/templates/content/en/LandingPage/how-it-works.mdx +0 -143
  247. package/templates/content/en/ReturnsPolicyPage/returns.mdx +0 -108
  248. package/templates/content/en/ShippingPolicyPage/shipping.mdx +0 -108
@@ -1,5 +1,44 @@
1
1
  import { classifyModules } from "./lib/module-tier.js";
2
2
  import { MODULE_SLICES, normalizeModules, } from "./module-architecture.js";
3
+ /**
4
+ * The closed **shape** enum (starter-template contract § The three axes,
5
+ * operator-disposed 2026-09-24) — `<primary node>-<offering node>` in
6
+ * schema.org terms, kebab-cased. Declared beside `TemplateManifest`, the same
7
+ * pattern `ModuleName` uses in `parse-args.ts`: a `readonly [...] as const`
8
+ * array plus its derived literal-union type, so an unrecognized value fails
9
+ * type-check rather than a runtime validation.
10
+ */
11
+ export const STARTER_SHAPES = [
12
+ "organization-product",
13
+ "organization-service",
14
+ "local-business-service",
15
+ "local-business-product",
16
+ "person-service",
17
+ ];
18
+ /**
19
+ * The closed **design** enum (same contract section) — the look applied to
20
+ * any shape; orthogonal to it by construction.
21
+ */
22
+ 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
+ };
3
42
  /**
4
43
  * The base layer's directory under `site-templates/` (#5618 D1) — the seed
5
44
  * files every template reaches by declaring `extends`, installed by
@@ -12,6 +51,16 @@ import { MODULE_SLICES, normalizeModules, } from "./module-architecture.js";
12
51
  * the directory-scanning tests key their skip on.
13
52
  */
14
53
  export const BASE_LAYER_DIR = "_base";
54
+ /**
55
+ * Whether a merged roster declares one scaffold-relative content file
56
+ * (#6646 D1): named by a row's `content`, or under a row's `contentDir`.
57
+ * The one predicate the scaffold's per-row install and the manifest
58
+ * generator's install-diff both read, so the two cannot disagree.
59
+ */
60
+ export function rosterDeclares(roster, path) {
61
+ return roster.some((row) => (row.content ?? []).includes(path) ||
62
+ (row.contentDir !== undefined && path.startsWith(`${row.contentDir}/`)));
63
+ }
15
64
  /** Thrown on an undeclared template name or a route claimed twice. Mirrors `SurfaceError`'s shape. */
16
65
  export class TemplateError extends Error {
17
66
  constructor(message) {
@@ -20,32 +69,62 @@ export class TemplateError extends Error {
20
69
  }
21
70
  }
22
71
  /**
23
- * The ten rows every declared template brings itself — one declared page set,
24
- * whichever mechanism scaffolds each (the Free manifest § 3, #5469 D4).
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`.
78
+ */
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
+ ];
87
+ /**
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:
25
98
  *
26
- * Two mechanisms, one roster:
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).
27
116
  *
28
- * - `/`, `/how-it-works`, `/features`, `/about`, `/faq`, `/contact` and
29
- * `/sign-in` are in EVERY scaffold, template or not: their content is the
30
- * always-copied overlay (`template-overlay/content/en/**`, copied whole by
31
- * `build-templates.mjs`) and their routes are core (`CORE_ROUTE_DIRS` —
32
- * the three LandingPage pages render through the generic `[slug]` route,
33
- * #5469 D2). They are declared here anyway, so the manifest a template
34
- * publishes (`build-template-manifest.mjs`) lists the whole page set and
35
- * the roster is what a template IS, not merely what it adds.
36
- * - `/pricing`, `/privacy` and `/terms` are module-less by construction
37
- * (parent § D16): no module owns these surfaces, their route files live in
38
- * `apps/web` and are snapshotted into the starter like every other route,
39
- * and a template enables them by NAMING them in its roster — `scaffold()`
40
- * selects their `GATEABLE_SURFACES` entries from the merged roster's
41
- * routes (#5469 D10). Their seed content ships ONCE, in the base layer
42
- * (`site-templates/_base/content/**`, #5618 D1), which every template
43
- * reaches by declaring `extends` — installed before the module bundles
44
- * and the template's own tree, so a template may override a base page
45
- * by shipping the same path.
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).
46
122
  *
47
- * `/sign-in` carries no `content`: it is a code route (the SignInCard island),
48
- * like the API-backed rows the scheduling module contributes.
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.
49
128
  *
50
129
  * `/blog` is deliberately NOT here (#5469 D3): the docs-blog module
51
130
  * contributes its own `/blog` row, so a base row would make `mergeRoster`
@@ -54,24 +133,33 @@ export class TemplateError extends Error {
54
133
  */
55
134
  const TEMPLATE_BASE_ROSTER = [
56
135
  { route: "/", content: ["content/en/HomePage/home.mdx"] },
136
+ { route: "/software" },
137
+ { route: "/software/[slug]", contentDir: "content/en/SoftwareProduct" },
57
138
  {
58
- route: "/how-it-works",
59
- content: ["content/en/LandingPage/how-it-works.mdx"],
139
+ route: "/use-cases",
140
+ content: ["content/en/UseCasesList/use-cases.mdx"],
60
141
  },
61
- { route: "/features", content: ["content/en/LandingPage/features.mdx"] },
142
+ { route: "/use-cases/[slug]", contentDir: "content/en/UseCase" },
62
143
  { route: "/pricing", content: ["content/en/Pricing/pricing.mdx"] },
63
144
  { route: "/about", content: ["content/en/AboutPage/about.mdx"] },
64
145
  { route: "/faq", content: ["content/en/FAQPage/faq.mdx"] },
65
146
  { route: "/contact", content: ["content/en/ContactPage/contact.mdx"] },
147
+ ...SHAPE_INDEPENDENT_ROWS,
66
148
  { route: "/sign-in" },
149
+ { route: "/sign-up" },
67
150
  { route: "/privacy", content: ["content/en/PrivacyPage/privacy.mdx"] },
68
151
  { route: "/terms", content: ["content/en/TermsPage/terms.mdx"] },
152
+ {
153
+ route: "/purchase-policy",
154
+ content: ["content/en/PurchasePolicyPage/purchase-policy.mdx"],
155
+ },
69
156
  ];
70
157
  /**
71
- * The Free `/blog` teaser (#5469 D3) — one static LandingPage (`schema: Blog`)
72
- * that says what the Pro docs-blog module gives you. It is served by the
73
- * generic `[slug]` route over `content/en/LandingPage/blog.mdx`, which ships
74
- * in the always-copied overlay. `/blog` degrades by DIRECTORY PRECEDENCE, not
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
75
163
  * by a module gate: in a keyed docs-blog scaffold the module's own
76
164
  * `blog/[[...slug]]/page.tsx` directory exists and Next.js resolves the static
77
165
  * segment first, so the teaser file is inert; keyless, no `blog/` directory
@@ -80,16 +168,36 @@ const TEMPLATE_BASE_ROSTER = [
80
168
  */
81
169
  const FREE_BLOG_TEASER_ROW = {
82
170
  route: "/blog",
83
- content: ["content/en/LandingPage/blog.mdx"],
171
+ content: ["content/en/BlogList/blog.mdx"],
84
172
  };
173
+ /**
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`).
182
+ */
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
+ ];
85
188
  /**
86
189
  * `minimalism-site`'s own rows (#5589) — the one declared template that does
87
190
  * not reuse `TEMPLATE_BASE_ROSTER`. Its page set is the issue's ROSTER, and
88
191
  * the rows it does NOT carry are as deliberate as the ones it does: no
89
- * `/how-it-works`, `/features` or `/pricing` (a reports-first site sells
192
+ * `/how-it-works`, product page or `/pricing` (a reports-first site sells
90
193
  * nothing from a pricing table), and no `/sign-in` (a code route every
91
194
  * scaffold carries regardless — `CORE_ROUTE_DIRS` — which this template
92
- * simply does not advertise as one of its pages).
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.
93
201
  *
94
202
  * `/meet`, `/meet/[slug]`, `/meet/confirmed`, `/reports/[slug]` and the three
95
203
  * `/flows/*` rows are absent here on purpose: they are the `scheduling` and
@@ -102,20 +210,26 @@ const MINIMALISM_ROSTER = [
102
210
  { route: "/reports", content: ["content/en/LandingPage/reports.mdx"] },
103
211
  { route: "/about", content: ["content/en/AboutPage/about.mdx"] },
104
212
  { route: "/services", content: ["content/en/ServicesList/services.mdx"] },
105
- { route: "/services/[slug]" },
213
+ { route: "/services/[slug]", contentDir: "content/en/Service" },
106
214
  { route: "/contact", content: ["content/en/ContactPage/contact.mdx"] },
107
215
  { route: "/referral", content: ["content/en/LandingPage/referral.mdx"] },
108
216
  { route: "/faq", content: ["content/en/FAQPage/faq.mdx"] },
217
+ ...SHAPE_INDEPENDENT_ROWS,
109
218
  { route: "/privacy", content: ["content/en/PrivacyPage/privacy.mdx"] },
110
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,
111
225
  ];
112
226
  /**
113
227
  * `simple-service`'s own rows (#6087) — the T1 lawn-care roster of Epic #5778.
114
228
  * The second declared template that does not reuse `TEMPLATE_BASE_ROSTER`: a
115
229
  * lawn-care site sells three named services from `/services`, not from a
116
- * pricing table, so `/how-it-works`, `/features` and `/pricing` are absent —
117
- * `/pricing`'s `_base` seed still lands on disk, it simply has no rostered
118
- * route, exactly as it does for `minimalism-site`.
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`.
119
233
  *
120
234
  * `/meet`, `/meet/[slug]`, `/meet/confirmed` and `/appointments` are absent
121
235
  * here on purpose: they are the `scheduling` module's own
@@ -135,33 +249,35 @@ const MINIMALISM_ROSTER = [
135
249
  * claimant, the template's own roster is the only place that can declare
136
250
  * them, and doing so trips no collision.
137
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
+ *
138
258
  * `/account` carries no `content`: it is a code route (the `(authed)` group),
139
259
  * like `/sign-in` on the base roster and like the API-backed rows the
140
- * scheduling module contributes. `/services/[slug]` likewise declares none —
141
- * its seed is a whole DIRECTORY (`content/en/Service`), and a roster row's
142
- * `content` names files the manifest generator reads frontmatter from.
260
+ * scheduling module contributes. `/services/[slug]` declares its seed
261
+ * DIRECTORY (`contentDir`, `content/en/Service`) — the three service pages.
143
262
  */
144
263
  const SIMPLE_SERVICE_ROSTER = [
145
264
  { route: "/", content: ["content/en/HomePage/home.mdx"] },
146
265
  { route: "/services", content: ["content/en/ServicesList/services.mdx"] },
147
- { route: "/services/[slug]" },
266
+ { route: "/services/[slug]", contentDir: "content/en/Service" },
148
267
  { route: "/about", content: ["content/en/AboutPage/about.mdx"] },
149
268
  { route: "/contact", content: ["content/en/ContactPage/contact.mdx"] },
150
269
  { route: "/faq", content: ["content/en/FAQPage/faq.mdx"] },
151
- { route: "/returns", content: ["content/en/ReturnsPolicyPage/returns.mdx"] },
270
+ ...SHAPE_INDEPENDENT_ROWS,
152
271
  {
153
- route: "/cancellation",
154
- content: ["content/en/CancellationPolicyPage/cancellation.mdx"],
155
- },
156
- {
157
- route: "/shipping",
158
- content: ["content/en/ShippingPolicyPage/shipping.mdx"],
272
+ route: "/purchase-policy",
273
+ content: ["content/en/PurchasePolicyPage/purchase-policy.mdx"],
159
274
  },
160
275
  { route: "/privacy", content: ["content/en/PrivacyPage/privacy.mdx"] },
161
276
  { route: "/terms", content: ["content/en/TermsPage/terms.mdx"] },
162
277
  { route: "/account" },
163
278
  { route: "/purchase/success" },
164
279
  { route: "/purchase/cancel" },
280
+ { route: "/thanks/[slug]" },
165
281
  ];
166
282
  /**
167
283
  * The three settled templates and their fixed `requires` lists — settled by
@@ -194,7 +310,12 @@ export const TEMPLATE_MANIFESTS = Object.freeze({
194
310
  "marketing-site-free": {
195
311
  name: "marketing-site-free",
196
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 },
197
316
  categories: ["accounting", "legal", "technology"],
317
+ shape: "organization-product",
318
+ design: "standard",
198
319
  extends: BASE_LAYER_DIR,
199
320
  roster: [...TEMPLATE_BASE_ROSTER, FREE_BLOG_TEASER_ROW],
200
321
  },
@@ -221,8 +342,10 @@ export const TEMPLATE_MANIFESTS = Object.freeze({
221
342
  "technology",
222
343
  "medical-specialty",
223
344
  ],
345
+ shape: "organization-product",
346
+ design: "standard",
224
347
  extends: BASE_LAYER_DIR,
225
- roster: TEMPLATE_BASE_ROSTER,
348
+ roster: [...TEMPLATE_BASE_ROSTER, ...SIGNAL_COLLECTORS_SAMPLE_ROWS],
226
349
  },
227
350
  /**
228
351
  * Pro Plus — the Pro list plus `multi-tenant`, which is correctly in the
@@ -243,8 +366,10 @@ export const TEMPLATE_MANIFESTS = Object.freeze({
243
366
  "multi-tenant",
244
367
  ],
245
368
  categories: ["technology", "medical-specialty"],
369
+ shape: "organization-product",
370
+ design: "standard",
246
371
  extends: BASE_LAYER_DIR,
247
- roster: TEMPLATE_BASE_ROSTER,
372
+ roster: [...TEMPLATE_BASE_ROSTER, ...SIGNAL_COLLECTORS_SAMPLE_ROWS],
248
373
  },
249
374
  /**
250
375
  * The second Pro template (#5589) — the reports-first, scroll-snap deck
@@ -256,11 +381,17 @@ export const TEMPLATE_MANIFESTS = Object.freeze({
256
381
  *
257
382
  * It declares its OWN roster rather than reusing `TEMPLATE_BASE_ROSTER`
258
383
  * (the only declared template that does): the deck home replaces the
259
- * document-scroll home, and `/how-it-works`, `/features`, `/pricing` and
260
- * `/sign-in` are not pages this site has. `/pricing`'s absence is not a
261
- * missing file — the `_base` layer still lands its seed content, and the
262
- * roster is what the template IS, so an unrostered page simply has no
263
- * route directory copied (`rosterSurfaces`).
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.
264
395
  *
265
396
  * `/reports` and `/referral` carry no `GATEABLE_SURFACES` entry and need
266
397
  * none: both are single-segment `LandingPage` slugs served by the generic
@@ -268,24 +399,18 @@ export const TEMPLATE_MANIFESTS = Object.freeze({
268
399
  * uses. `/reports/[slug]` (the reader) is the signal-collectors module's
269
400
  * own contributed row.
270
401
  *
271
- * `/services/[slug]` declares no `content`: its seed is a whole DIRECTORY
272
- * (`content/en/Service`, as `routes-map.ts` records it), and a roster
273
- * row's `content` names files the manifest generator reads frontmatter
274
- * from. The three samples ride the template tree unmapped, landing at
275
- * that directory.
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.
276
405
  */
277
406
  "minimalism-site": {
278
407
  name: "minimalism-site",
279
408
  requires: ["marketing", "scheduling", "signal-collectors"],
280
409
  categories: ["real-estate", "technology", "legal"],
410
+ shape: "organization-service",
411
+ design: "minimalism",
281
412
  extends: BASE_LAYER_DIR,
282
413
  roster: MINIMALISM_ROSTER,
283
- layout: {
284
- nav: "mark-only",
285
- footer: "centered",
286
- announcementBanner: false,
287
- home: "deck",
288
- },
289
414
  },
290
415
  /**
291
416
  * The T1 lawn-care template (#6087, Epic #5778) — the first site shape
@@ -303,14 +428,16 @@ export const TEMPLATE_MANIFESTS = Object.freeze({
303
428
  * `scheduling` and `commerce` are both paid, so `templateDelivery`
304
429
  * returns `"paid"` from either one alone.
305
430
  *
306
- * It declares NO `layout` block: the default marketing-site chrome is the
307
- * one a lawn-care site wants, and the issue's STOP boundary excludes
308
- * layout knobs the spec does not name.
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.
309
434
  */
310
435
  "simple-service": {
311
436
  name: "simple-service",
312
437
  requires: ["marketing", "scheduling", "commerce"],
313
438
  categories: ["landscaping"],
439
+ shape: "local-business-service",
440
+ design: "standard",
314
441
  extends: BASE_LAYER_DIR,
315
442
  roster: SIMPLE_SERVICE_ROSTER,
316
443
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ailk",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Scaffolder for AI Launch Kit — `pnpm create ailk@latest <dir>` produces a starter repo consuming @working-theory/* from npm (per ADR 0007).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,8 +25,8 @@
25
25
  "tar": "^7.5.21"
26
26
  },
27
27
  "devDependencies": {
28
- "@working-theory/schema": "0.25.0",
29
- "@working-theory/templates": "0.25.0"
28
+ "@working-theory/schema": "0.27.0",
29
+ "@working-theory/templates": "0.27.0"
30
30
  },
31
31
  "repository": {
32
32
  "type": "git",
@@ -58,6 +58,8 @@
58
58
  "build-catalog": "node scripts/build-component-catalog.mjs",
59
59
  "check-catalog": "node scripts/build-component-catalog.mjs --check",
60
60
  "manifest:build": "node scripts/build-template-manifest.mjs --template marketing-site-free",
61
- "manifest:check": "node scripts/build-template-manifest.mjs --template marketing-site-free --check && node scripts/build-template-manifest.mjs --template marketing-site --check"
61
+ "manifest:check": "node scripts/build-template-manifest.mjs --template marketing-site-free --check && node scripts/build-template-manifest.mjs --template marketing-site --check",
62
+ "page-types:build": "tsx scripts/generate-page-types-reference.ts",
63
+ "page-types:check": "tsx scripts/generate-page-types-reference.ts --check"
62
64
  }
63
65
  }
@@ -0,0 +1,53 @@
1
+ ---
2
+ type: "BookCall"
3
+ slug: "book-call"
4
+ title: "{Book a call: what a visitor gets from it}"
5
+ description: "{One sentence on who the call is for and what they leave with}"
6
+ placeholder: true
7
+ schema:
8
+ mainEntity:
9
+ "@type": "Offer"
10
+ name: "{Your call name}"
11
+ price: 0
12
+ priceCurrency: "USD"
13
+ sections:
14
+ [
15
+ {
16
+ "id": "hero",
17
+ "type": "hero",
18
+ "data":
19
+ {
20
+ "eyebrow": "Book a call",
21
+ "headline": "{Book a call: what a visitor gets from it}",
22
+ "subheadline": "{One sentence on who the call is for and what they leave with}",
23
+ "primaryCta": { "label": "Get in touch", "href": "/contact" },
24
+ },
25
+ },
26
+ {
27
+ "id": "what-happens-on-the-call",
28
+ "type": "items",
29
+ "data":
30
+ {
31
+ "headline": "What happens on the call?",
32
+ "columns": 3,
33
+ "items":
34
+ [
35
+ {
36
+ "title": "{The first thing you cover}",
37
+ "description": "{One or two sentences on what the visitor learns or decides here}",
38
+ },
39
+ {
40
+ "title": "{The second thing you cover}",
41
+ "description": "{One or two sentences on what the visitor learns or decides here}",
42
+ },
43
+ {
44
+ "title": "{The next step you agree}",
45
+ "description": "{One or two sentences on what happens after the call}",
46
+ },
47
+ ],
48
+ },
49
+ },
50
+ ]
51
+ ---
52
+
53
+ {/* Rendered from frontmatter `sections[]` by the /book-a-call route. The call to action sends visitors to your contact page; a template that carries the scheduling module overrides this file to point it at the /meet booker. The `schema:` Offer is the page's JSON-LD main entity — a free call. Placeholder copy — replace every {brace} with your own words before you launch. */}
@@ -0,0 +1,66 @@
1
+ ---
2
+ type: "GlossaryList"
3
+ slug: "glossary"
4
+ title: "{Glossary: the terms your customers look up}"
5
+ description: "{One sentence on the subject these terms cover and who they help}"
6
+ placeholder: true
7
+ sections:
8
+ [
9
+ {
10
+ "id": "hero",
11
+ "type": "hero",
12
+ "data":
13
+ {
14
+ "eyebrow": "Glossary",
15
+ "headline": "{Glossary: the terms your customers look up}",
16
+ "subheadline": "{One sentence on the subject these terms cover and who they help}",
17
+ "primaryCta": { "label": "{Primary CTA}", "href": "/contact" },
18
+ },
19
+ },
20
+ {
21
+ "id": "glossary-index",
22
+ "type": "glossary-index",
23
+ "data":
24
+ {
25
+ "headline": "{Terms, A to Z}",
26
+ "description": "{One sentence on how to use this list}",
27
+ "variant": "columns",
28
+ "terms":
29
+ [
30
+ {
31
+ "term": "{Term 1}",
32
+ "slug": "term-1",
33
+ "definition": "{One plain sentence that defines this term the way your customers would say it}",
34
+ "letter": "T",
35
+ },
36
+ {
37
+ "term": "{Term 2}",
38
+ "slug": "term-2",
39
+ "definition": "{One plain sentence that defines this term the way your customers would say it}",
40
+ "letter": "T",
41
+ },
42
+ {
43
+ "term": "{Term 3}",
44
+ "slug": "term-3",
45
+ "definition": "{One plain sentence that defines this term the way your customers would say it}",
46
+ "letter": "T",
47
+ },
48
+ {
49
+ "term": "{Term 4}",
50
+ "slug": "term-4",
51
+ "definition": "{One plain sentence that defines this term the way your customers would say it}",
52
+ "letter": "T",
53
+ },
54
+ {
55
+ "term": "{Term 5}",
56
+ "slug": "term-5",
57
+ "definition": "{One plain sentence that defines this term the way your customers would say it}",
58
+ "letter": "T",
59
+ },
60
+ ],
61
+ },
62
+ },
63
+ ]
64
+ ---
65
+
66
+ {/* Rendered from frontmatter `sections[]` by the /glossary route. Each `glossary-index` term becomes a DefinedTerm in the page's JSON-LD DefinedTermSet, so write the definition an agent should quote. Placeholder copy — replace every {brace} with your own words, and add or remove terms, before you launch. */}
@@ -0,0 +1,68 @@
1
+ ---
2
+ type: "NewsletterSignup"
3
+ slug: "newsletter"
4
+ title: "{Newsletter: what subscribers get and how often}"
5
+ description: "{One sentence on what this newsletter covers and who it is for}"
6
+ placeholder: true
7
+ schema:
8
+ mainEntity:
9
+ "@type": "Offer"
10
+ name: "{Your newsletter name}"
11
+ price: 0
12
+ priceCurrency: "USD"
13
+ sections:
14
+ [
15
+ {
16
+ "id": "hero",
17
+ "type": "hero",
18
+ "data":
19
+ {
20
+ "eyebrow": "Newsletter",
21
+ "headline": "{Newsletter: what subscribers get and how often}",
22
+ "subheadline": "{One sentence on what this newsletter covers and who it is for}",
23
+ "primaryCta": { "label": "Subscribe", "href": "#signup" },
24
+ },
25
+ },
26
+ {
27
+ "id": "signup",
28
+ "type": "newsletter",
29
+ "data":
30
+ {
31
+ "headline": "Subscribe",
32
+ "description": "{One sentence on what lands in the inbox}",
33
+ "action": "/v1/leads/subscribe",
34
+ "source": "newsletter",
35
+ "placeholder": "you@example.com",
36
+ "submitLabel": "Subscribe",
37
+ "consentText": "{One line on how you use the address, and that readers can unsubscribe any time}",
38
+ "pattern": "none",
39
+ },
40
+ },
41
+ {
42
+ "id": "faq",
43
+ "type": "faq",
44
+ "data":
45
+ {
46
+ "headline": "FAQs",
47
+ "presentation": "expanded",
48
+ "items":
49
+ [
50
+ {
51
+ "question": "{How often will I hear from you?}",
52
+ "answer": "{Answer in one or two plain sentences}",
53
+ },
54
+ {
55
+ "question": "{What will I get?}",
56
+ "answer": "{Answer in one or two plain sentences}",
57
+ },
58
+ {
59
+ "question": "{Can I unsubscribe?}",
60
+ "answer": "{Answer in one or two plain sentences}",
61
+ },
62
+ ],
63
+ },
64
+ },
65
+ ]
66
+ ---
67
+
68
+ {/* Rendered from frontmatter `sections[]` by the /newsletter route. The form posts the address to lead capture (`/v1/leads/subscribe`), which records a subscriber and sends no verification email. The `schema:` Offer is the page's JSON-LD main entity — a free subscription. Placeholder copy — replace every {brace} with your own words before you launch. */}