@gradial/aci 0.1.52-rc.9 → 0.1.52

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 (254) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +86 -5
  3. package/dist/astro/error-page.js +1 -0
  4. package/dist/astro/index.js +4 -0
  5. package/dist/astro/page.d.ts +5 -2
  6. package/dist/astro/page.js +35 -23
  7. package/dist/astro/preview.js +2 -17
  8. package/dist/audience-data-models.d.ts +308 -197
  9. package/dist/audience-data-models.js +313 -51
  10. package/dist/cli/compile-registry.mjs +6 -34
  11. package/dist/cli/mintlify-render.mjs +8 -0
  12. package/dist/collect/aci.js +1 -1
  13. package/dist/collect/aci.js.sha256 +1 -1
  14. package/dist/collect/api.d.ts +6 -7
  15. package/dist/collect/api.js +21 -8
  16. package/dist/collect/assignments.d.ts +7 -9
  17. package/dist/collect/assignments.js +23 -32
  18. package/dist/collect/batcher.d.ts +9 -7
  19. package/dist/collect/batcher.js +6 -5
  20. package/dist/collect/consent/envelope.d.ts +7 -1
  21. package/dist/collect/consent/envelope.js +3 -1
  22. package/dist/collect/consent/mapping.d.ts +2 -0
  23. package/dist/collect/consent/mapping.js +1 -0
  24. package/dist/collect/consenthold.d.ts +38 -0
  25. package/dist/collect/consenthold.js +82 -0
  26. package/dist/collect/declarative.js +7 -5
  27. package/dist/collect/gapbuffer.d.ts +13 -4
  28. package/dist/collect/gapbuffer.js +21 -10
  29. package/dist/collect/index.js +78 -21
  30. package/dist/collect/pageviews.d.ts +11 -11
  31. package/dist/collect/pageviews.js +27 -22
  32. package/dist/consent-banner/index.d.ts +17 -0
  33. package/dist/consent-banner/index.js +23 -0
  34. package/dist/content/index.d.ts +2 -1
  35. package/dist/content/index.js +4 -1
  36. package/dist/content/provider.d.ts +16 -2
  37. package/dist/content/provider.js +5 -1
  38. package/dist/content/resolve-slots.d.ts +24 -2
  39. package/dist/content/resolve-slots.js +127 -21
  40. package/dist/content/types.d.ts +5 -1
  41. package/dist/edge/expression-capabilities.v1.d.ts +106 -0
  42. package/dist/edge/expression-capabilities.v1.js +165 -0
  43. package/dist/index.d.ts +1 -2
  44. package/dist/index.js +0 -1
  45. package/dist/internal/domain/edge/identity.d.ts +47 -4
  46. package/dist/internal/domain/edge/identity.js +130 -26
  47. package/dist/internal/domain/edge/index.d.ts +1 -1
  48. package/dist/internal/domain/edge/index.js +1 -1
  49. package/dist/internal/domain/edge/mimic_token.d.ts +14 -0
  50. package/dist/internal/domain/edge/mimic_token.js +125 -0
  51. package/dist/internal/domain/edge/personalization-core.d.ts +61 -22
  52. package/dist/internal/domain/edge/personalization-core.js +790 -200
  53. package/dist/internal/domain/edge/personalization_index.d.ts +17 -0
  54. package/dist/internal/domain/edge/personalization_index.js +55 -0
  55. package/dist/internal/domain/edge/resolve.js +76 -39
  56. package/dist/internal/domain/edge/testdata/flat_only_validator_v1.d.ts +10 -0
  57. package/dist/internal/domain/edge/testdata/flat_only_validator_v1.js +253 -0
  58. package/dist/internal/domain/edge/types.d.ts +82 -78
  59. package/dist/internal/domain/edge/types.js +3 -3
  60. package/dist/internal/domain/edge/variant_key.d.ts +4 -0
  61. package/dist/internal/domain/edge/variant_key.js +56 -0
  62. package/dist/internal/platform/collectionstore/errors.d.ts +24 -0
  63. package/dist/internal/platform/collectionstore/errors.js +48 -0
  64. package/dist/mintlify/blocks.d.ts +6 -0
  65. package/dist/mintlify/blocks.js +201 -0
  66. package/dist/mintlify/config.d.ts +3 -0
  67. package/dist/mintlify/config.js +77 -0
  68. package/dist/mintlify/index.d.ts +3 -0
  69. package/dist/mintlify/index.js +2 -0
  70. package/dist/mintlify/json.d.ts +8 -0
  71. package/dist/mintlify/json.js +37 -0
  72. package/dist/mintlify/materialize.d.ts +14 -0
  73. package/dist/mintlify/materialize.js +254 -0
  74. package/dist/mintlify/types.d.ts +74 -0
  75. package/dist/mintlify/types.js +1 -0
  76. package/dist/mintlify/validate.d.ts +7 -0
  77. package/dist/mintlify/validate.js +256 -0
  78. package/dist/next/assignments-navigation.d.ts +4 -0
  79. package/dist/next/assignments-navigation.js +18 -0
  80. package/dist/next/assignments-script.d.ts +3 -2
  81. package/dist/next/assignments-script.js +5 -5
  82. package/dist/next/error-pages.d.ts +4 -0
  83. package/dist/next/error-pages.js +4 -0
  84. package/dist/next/error-seat-payload.d.ts +2 -1
  85. package/dist/next/error-seat-payload.js +14 -24
  86. package/dist/next/error-seat.js +7 -2
  87. package/dist/next/index.d.ts +1 -1
  88. package/dist/next/index.js +3 -1
  89. package/dist/next/page.d.ts +2 -1
  90. package/dist/next/page.js +15 -4
  91. package/dist/next/release-context.d.ts +30 -0
  92. package/dist/next/release-context.js +56 -0
  93. package/dist/next/render.d.ts +1 -1
  94. package/dist/next/render.js +15 -5
  95. package/dist/next/server.d.ts +9 -3
  96. package/dist/next/server.js +17 -32
  97. package/dist/preview/core.d.ts +1 -0
  98. package/dist/preview/core.js +27 -0
  99. package/dist/react/VideoPlayer.d.ts +3 -3
  100. package/dist/react/index.d.ts +3 -3
  101. package/dist/registry.d.ts +4 -2
  102. package/dist/testing/conformance/checks.d.ts +2 -2
  103. package/dist/testing/conformance/checks.js +2 -0
  104. package/dist/testing/conformance/index.d.ts +1 -0
  105. package/dist/testing/conformance/index.js +1 -0
  106. package/dist/testing/conformance/next-stubs.d.ts +6 -0
  107. package/dist/testing/conformance/next-stubs.js +9 -0
  108. package/dist/testing/conformance/preset.d.ts +3 -2
  109. package/dist/testing/conformance/preset.js +15 -8
  110. package/dist/testing/conformance/root-identity.d.ts +31 -0
  111. package/dist/testing/conformance/root-identity.js +361 -0
  112. package/dist/testing/conformance/stubs/next-image.d.ts +7 -0
  113. package/dist/testing/conformance/stubs/next-image.js +5 -0
  114. package/dist/testing/conformance/stubs/next-link.d.ts +7 -0
  115. package/dist/testing/conformance/stubs/next-link.js +4 -0
  116. package/dist/testing/conformance/types.d.ts +5 -2
  117. package/dist/testing/index.d.ts +1 -1
  118. package/dist/testing/index.js +4 -3
  119. package/dist/types/block-ref.js +2 -2
  120. package/dist/types/component.d.ts +9 -0
  121. package/dist/types/config.d.ts +0 -1
  122. package/dist/types/index.d.ts +0 -1
  123. package/dist/types/index.js +0 -1
  124. package/dist/types/page.d.ts +0 -1
  125. package/migrations/conformance-registry.md +22 -0
  126. package/migrations/experiment-goal-ids.md +43 -0
  127. package/package.json +21 -7
  128. package/src/cli/compile-registry.mjs +6 -30
  129. package/src/cli/mintlify-render.mjs +9 -0
  130. package/src/types/block-ref.test.ts +16 -1
  131. package/src/types/block-ref.ts +2 -2
  132. package/src/types/component.ts +10 -0
  133. package/src/types/config.ts +0 -1
  134. package/src/types/index.ts +0 -1
  135. package/src/types/page.ts +0 -1
  136. package/templates/astro/starter-content/src/components/chrome/Footer.astro +5 -3
  137. package/templates/astro/starter-content/src/components/chrome/Header.astro +5 -3
  138. package/templates/astro/starter-content/src/components/elements/Button.astro +5 -2
  139. package/templates/astro/starter-content/src/components/elements/ButtonBlock.astro +5 -3
  140. package/templates/astro/starter-content/src/components/sections/ContainerSection.astro +4 -3
  141. package/templates/astro/starter-content/src/components/sections/FeatureCard.astro +4 -3
  142. package/templates/astro/starter-content/src/components/sections/FeatureGrid.astro +4 -3
  143. package/templates/astro/starter-content/src/components/sections/HomeHero.astro +5 -3
  144. package/templates/astro/starter-content/src/components/sections/PersonalizationChoice.astro +4 -3
  145. package/templates/astro/starter-content/src/components/sections/StatsBar.astro +4 -3
  146. package/templates/astro/starter-content/src/fixtures/sections.ts +1 -1
  147. package/templates/astro/starter-content/src/stories/CompositionStoryFrame.astro +7 -2
  148. package/templates/astro/template/.aci.yaml.tmpl +0 -1
  149. package/templates/astro/template/package.json.tmpl +5 -5
  150. package/templates/astro/template/src/layouts/SiteLayout.astro +2 -2
  151. package/templates/common/.agents/skills/aci/SKILL.md +58 -1
  152. package/templates/common/.agents/skills/authoring/SKILL.md +194 -42
  153. package/templates/common/.agents/skills/authoring/references/block-registry.md +19 -3
  154. package/templates/common/.agents/skills/authoring/references/contracts.md +9 -30
  155. package/templates/common/.agents/skills/authoring/references/repo-structure.md +5 -4
  156. package/templates/common/.agents/skills/authoring/references/sdk-server-functions.md +3 -3
  157. package/templates/common/.agents/skills/dam-delivery/SKILL.md +216 -0
  158. package/templates/common/.agents/skills/dam-delivery/references/permalink-grammar.md +167 -0
  159. package/templates/common/.agents/skills/migration/references/canon-migration.md +18 -67
  160. package/templates/common/.agents/skills/migration/references/selector-map.md +1 -1
  161. package/templates/common/.content/fragments/navbar/_index.json +24 -0
  162. package/templates/common/.content/fragments/site-footer/_index.json +61 -0
  163. package/templates/common/.content/pages/404/_index.json +4 -2
  164. package/templates/common/.content/pages/_index.json +0 -1
  165. package/templates/common/.content/pages/error/_index.json +4 -2
  166. package/templates/common/.content/schemas/site.schema.json +11 -1
  167. package/templates/common/.content/templates/error.json +24 -0
  168. package/templates/common/.content/templates/home.json +28 -0
  169. package/templates/common/.content/templates/site.json +8 -0
  170. package/templates/common/.content/templates/standard.json +24 -0
  171. package/templates/common/.nvmrc +1 -1
  172. package/templates/common/starter-content/.content/config/site.json +1 -1
  173. package/templates/common/starter-content/.content/dam/README.md +74 -0
  174. package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000101}/asset.json +1 -1
  175. package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000102}/asset.json +1 -1
  176. package/templates/common/starter-content/.content/pages/404/_index.json +1 -2
  177. package/templates/common/starter-content/.content/pages/{home/_index.json → _index.json} +14 -3
  178. package/templates/common/starter-content/.content/pages/about/_index.json +2 -2
  179. package/templates/common/starter-content/.content/pages/error/_index.json +1 -2
  180. package/templates/common/starter-content/.content/patterns/components/button.json +2 -2
  181. package/templates/common/starter-content/.content/patterns/components/container.json +8 -3
  182. package/templates/common/starter-content/.content/patterns/components/feature_card.json +2 -2
  183. package/templates/common/starter-content/.content/patterns/components/feature_grid.json +5 -3
  184. package/templates/common/starter-content/.content/patterns/components/home_hero.json +2 -2
  185. package/templates/common/starter-content/.content/patterns/components/personalization_choice.json +2 -2
  186. package/templates/common/starter-content/.content/patterns/components/site-footer.json +2 -2
  187. package/templates/common/starter-content/.content/patterns/components/site-navigation.json +2 -2
  188. package/templates/common/starter-content/.content/patterns/components/stats_bar.json +2 -2
  189. package/templates/common/starter-content/.content/schemas/site.schema.json +11 -1
  190. package/templates/nextjs/starter-content/src/components/chrome/Footer.tsx +4 -3
  191. package/templates/nextjs/starter-content/src/components/chrome/Header.tsx +4 -3
  192. package/templates/nextjs/starter-content/src/components/elements/Button.tsx +4 -3
  193. package/templates/nextjs/starter-content/src/components/elements/ButtonBlock.tsx +4 -3
  194. package/templates/nextjs/starter-content/src/components/sections/ContainerSection.tsx +4 -2
  195. package/templates/nextjs/starter-content/src/components/sections/FeatureCard.tsx +4 -3
  196. package/templates/nextjs/starter-content/src/components/sections/FeatureGrid.tsx +4 -2
  197. package/templates/nextjs/starter-content/src/components/sections/HomeHero.tsx +4 -3
  198. package/templates/nextjs/starter-content/src/components/sections/PersonalizationChoice.tsx +4 -2
  199. package/templates/nextjs/starter-content/src/components/sections/StatsBar.tsx +4 -3
  200. package/templates/nextjs/template/.aci.yaml +0 -1
  201. package/templates/nextjs/template/.aci.yaml.tmpl +0 -1
  202. package/templates/nextjs/template/package.json.tmpl +4 -3
  203. package/templates/nextjs/template/src/app/_components/c15t-bridge.ts +30 -0
  204. package/templates/nextjs/template/src/app/_components/c15t-consent-ui.tsx +37 -0
  205. package/templates/nextjs/template/src/app/_components/consent-manager.css +63 -0
  206. package/templates/nextjs/template/src/app/_components/consent-manager.tsx +76 -0
  207. package/templates/nextjs/template/src/app/fonts/inter/OFL.txt +93 -0
  208. package/templates/nextjs/template/src/app/fonts/inter/inter-latin.woff2 +0 -0
  209. package/templates/nextjs/template/src/app/fonts/space-grotesk/OFL.txt +93 -0
  210. package/templates/nextjs/template/src/app/fonts/space-grotesk/space-grotesk-latin.woff2 +0 -0
  211. package/templates/nextjs/template/src/app/layout.tsx +12 -7
  212. package/templates/nextjs/template/tests/conformance.test.ts.tmpl +1 -0
  213. package/dist/define-layout.d.ts +0 -6
  214. package/dist/define-layout.js +0 -14
  215. package/dist/edge/personalization-core.d.ts +0 -11
  216. package/dist/edge/personalization-core.js +0 -269
  217. package/dist/internal/domain/edge/compose.d.ts +0 -24
  218. package/dist/internal/domain/edge/compose.js +0 -89
  219. package/dist/internal/domain/edge/record/codec.d.ts +0 -71
  220. package/dist/internal/domain/edge/record/codec.js +0 -401
  221. package/dist/next/collect-route.d.ts +0 -19
  222. package/dist/next/collect-route.js +0 -459
  223. package/dist/next/edge-config.d.ts +0 -1
  224. package/dist/next/edge-config.js +0 -92
  225. package/dist/next/envelope-digest.d.ts +0 -21
  226. package/dist/next/envelope-digest.js +0 -58
  227. package/dist/next/middleware.d.ts +0 -40
  228. package/dist/next/middleware.js +0 -475
  229. package/dist/next/personalization-host.d.ts +0 -134
  230. package/dist/next/personalization-host.js +0 -270
  231. package/dist/next/root-layout.d.ts +0 -4
  232. package/dist/next/root-layout.js +0 -5
  233. package/dist/next/vercel-collect-sdk-route.d.ts +0 -27
  234. package/dist/next/vercel-collect-sdk-route.js +0 -181
  235. package/dist/next/vercel-edge-projection-v1.d.ts +0 -24
  236. package/dist/next/vercel-edge-projection-v1.js +0 -880
  237. package/dist/next/vercel-personalization.d.ts +0 -4
  238. package/dist/next/vercel-personalization.js +0 -4
  239. package/dist/next/vercel-s3-personalization.d.ts +0 -79
  240. package/dist/next/vercel-s3-personalization.js +0 -570
  241. package/dist/types/layout.d.ts +0 -24
  242. package/src/types/layout.ts +0 -29
  243. package/templates/astro/starter-content/src/render/RenderPage.astro +0 -67
  244. package/templates/astro/template/src/cms/contracts/layouts/index.ts +0 -18
  245. package/templates/common/starter-content/.content/patterns/layouts/default.json +0 -14
  246. package/templates/nextjs/starter-content/src/components/elements/AtomicBlock.tsx +0 -13
  247. package/templates/nextjs/template/src/cms/contracts/layouts/index.ts +0 -16
  248. /package/dist/{types/layout.js → cli/mintlify-render.d.mts} +0 -0
  249. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000101}/editorial.json +0 -0
  250. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000101}/governance.json +0 -0
  251. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000101}/versions/v0001.json +0 -0
  252. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000102}/editorial.json +0 -0
  253. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000102}/governance.json +0 -0
  254. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000102}/versions/v0001.json +0 -0
@@ -14,7 +14,9 @@ description: Author content and build components for ACI sites. Use when creatin
14
14
  | `.content/config/site.json` | Site metadata, SEO defaults |
15
15
  | `.content/config/robots.txt` | Authored crawler rules; publish bundles this file verbatim as `assets/robots.txt` when present |
16
16
  | `.content/pages/{slug}/_index.json` | Page content (slug maps to route) |
17
- | `.content/fragments/{id}/_index.json` | Shared fragments (navbar, footer — used by layout defaults) |
17
+ | `.content/fragments/{id}/_index.json` | Shared fragments (navbar, footer — usable by template defaults) |
18
+ | `.content/templates/{name}.json` | Page templates and global policies: the composition guardrails every page and variant must satisfy (see Page Templates) |
19
+ | `.content/overlays/{dimension}/{value}/pages/{slug}/_index.json` | Page overlays: keyed patches that personalize a page for an audience, locale, or experiment arm |
18
20
  | `.aci/schemas/page.schema.json` | Page validation schema (auto-generated from contracts) |
19
21
 
20
22
  ### Page Structure
@@ -26,20 +28,35 @@ Every page JSON requires:
26
28
  "$type": "page",
27
29
  "id": "unique-page-id",
28
30
  "status": "published",
29
- "layout": "default",
31
+ "template": "standard",
30
32
  "metadata": {
31
33
  "title": "Page Title",
32
34
  "description": "Meta description",
33
35
  "sitemap": { "visible": true }
34
36
  },
35
37
  "regions": {
36
- "main": []
38
+ "main": [
39
+ {
40
+ "$type": "component",
41
+ "id": "page-hero",
42
+ "component": "home_hero",
43
+ "props": {
44
+ "eyebrow": "Eyebrow",
45
+ "headline": "Page headline",
46
+ "description": "One sentence that says what this page is for.",
47
+ "ctaLabel": "Back to home",
48
+ "ctaHref": "/"
49
+ }
50
+ }
51
+ ]
37
52
  }
38
53
  }
39
54
  ```
40
55
 
41
56
  Base page fields are SDK-owned and included in the generated
42
- `.aci/schemas/page.schema.json`.
57
+ `.aci/schemas/page.schema.json`. `template` names the page template that
58
+ governs an interior page (see Page Templates); pages under a route a
59
+ template covers omit it.
43
60
 
44
61
  #### Sitemap visibility
45
62
 
@@ -67,10 +84,9 @@ the intent clear in content).
67
84
 
68
85
  This does **not** add a `noindex` meta tag — it only controls sitemap inclusion.
69
86
 
70
- The `layout` field is required — it determines which slots the page can
71
- fill. The `default` layout provides `header`/`main`/`footer` slots with
72
- fragment defaults for chrome (navbar, footer). Pages typically only fill
73
- `regions.main`; chrome comes from the layout's fragment defaults.
87
+ The bound template determines which regions the page can fill. A template may
88
+ provide `header` and `footer` fragment defaults, so pages typically author only
89
+ `regions.main`. There is no authored `layout` field or code layout registry.
74
90
 
75
91
  ### Route Mapping
76
92
 
@@ -78,19 +94,173 @@ Pages map to URL routes by their folder path under `/pages/`:
78
94
 
79
95
  | Page Path | Route |
80
96
  |-----------|-------|
81
- | `/pages/home/_index.json` | `/` |
97
+ | `/pages/_index.json` | `/` |
82
98
  | `/pages/about/_index.json` | `/about` |
83
99
  | `/pages/products/_index.json` | `/products` |
84
100
  | `/pages/products/shoes/_index.json` | `/products/shoes` |
85
101
 
86
- The `home` slug is special-cased to route `/`. All other slugs map directly.
102
+ The empty slug is the site root. Every named folder maps directly, including
103
+ `/pages/home/_index.json` at `/home`.
104
+
105
+ ### Page Templates and Global Policies
106
+
107
+ Composition guardrails live in `.content/templates/<name>.json`. They are
108
+ content, owned by whoever holds policy authority on `/templates/`, and every
109
+ page **and every personalized variant of it** must satisfy them before a
110
+ release can be published. Read the governing template before composing a
111
+ page: a write that breaks a guardrail is refused when you make it, with 422
112
+ `<lane>.template_invalid` and a `template/*` diagnostic naming the exact
113
+ block.
114
+
115
+ **How to read a refusal.** Each entry under `details.diagnostics` carries the
116
+ rule id, the document and JSON pointer to fix (`documentRef`, `jsonPointer`),
117
+ and in `details`: `template` (the governing document), `variant` (empty for
118
+ the base page), `expected` and `found`, and `trigger` — which paths of your
119
+ own batch caused the check. The list is capped at 100; `details.diagnosticCount`
120
+ is the real total. You are refused only for what your batch breaks: a page
121
+ that already failed before your write never blocks you, and fixing it is
122
+ someone with authority over `/pages/`.
123
+
124
+ **Check before you write.** `POST /v1/content/validate` takes the same
125
+ `changes` body as a branch write and returns the same verdict without
126
+ committing anything — use it to try a composition or a template edit.
127
+ `aci validate` runs the same evaluator over a local tree, and `POST
128
+ /v1/branches/{name}/coherence/check` over a branch. A promote to `live` is
129
+ judged the same way against `live`: a page must travel with the template it
130
+ needs, so widen the promote set when `template/not-found` comes back.
131
+
132
+ **Publish is still the guarantee.** A write is judged for the variant its own
133
+ path names. Two overlays that are each valid but violate together under a
134
+ wider variant are caught by the release preflight, which refuses the publish
135
+ with `publish.template_invalid` naming that variant; fix either overlay.
136
+
137
+ Two kinds of document, told apart by `$type`; the file stem is the name:
138
+
139
+ | Document | Kind | What it says |
140
+ |----------|------|--------------|
141
+ | `site.json` | `globalPolicy` | Applies to every page: `counts` (per-component `min`/`max` over the effective page, including nested blocks and rendered fragment placements). A `pageTemplate` may carry `counts` too, scoped to the pages it governs. |
142
+ | `home.json` | `pageTemplate` | Covers `routes: ["/"]`: `main` is exactly home_hero, feature_grid, personalization_choice, stats_bar, container, in that order. |
143
+ | `standard.json` | `pageTemplate` | Declared by interior pages: `main` starts with a home_hero, then `closed: false` admits anything after it. |
144
+ | `error.json` | `pageTemplate` | Covers `/404` and `/error`: a single home_hero. |
145
+
146
+ **Which template governs a page.** A page under a route a template's
147
+ `routes` covers is governed by that template and needs no declaration
148
+ (`/`, `/404`, `/error` in the starter). Every other page declares its
149
+ template with a root `"template": "<name>"` (the starter's `/about`
150
+ declares `standard`). An undeclared, uncovered page produces `template/unbound`
151
+ even without a global policy. Write/probe and promote gates refuse newly
152
+ introduced violations; publish refuses every unbound page. Route patterns are public
153
+ routes; a trailing `/*` covers every deeper route. A declaration on a
154
+ covered page must name the covering template.
155
+
156
+ **Steps.** A region rule is an ordered `sequence` of steps. Each step names
157
+ the inline `component`(s) it accepts, or the shared `fragment` ids
158
+ (`"fragment": ["faq-block"]`, or `"fragment": "*"` for any fragment no other
159
+ step names), or both, with `min`/`max` occurrences (default exactly one).
160
+ A step listing several components accepts any of them in any order up to
161
+ `max`. `closed: true` (the default) means nothing may follow the last step.
162
+ Template steps must be unambiguous: two adjacent optional steps may not
163
+ accept the same component. Only the keys shown here are accepted — a
164
+ misspelled key is refused as `template/invalid`, so a guardrail can never
165
+ switch itself off silently. A step may declare `"lock": "structure"`: variants
166
+ may change props, but must preserve the complete step assignment (ids,
167
+ components, order, fragment references, and nested block structure). Nested
168
+ blocks inside each locked top-level block need non-empty, distinct ids. The
169
+ comparison baseline includes the default-locale overlay; the home hero is locked.
170
+
171
+ A template that exercises the grammar, for an interior article page that
172
+ declares it with `"template": "article"`:
173
+
174
+ ```json
175
+ {
176
+ "$type": "pageTemplate",
177
+ "name": "article",
178
+ "description": "A hero, one to six body sections, then an optional shared promo.",
179
+ "regions": [
180
+ {
181
+ "name": "main",
182
+ "closed": false,
183
+ "sequence": [
184
+ { "id": "hero", "component": "home_hero" },
185
+ { "id": "body", "component": ["feature_grid", "stats_bar", "container"], "min": 1, "max": 6 },
186
+ { "id": "promo", "fragment": "*", "min": 0 }
187
+ ]
188
+ }
189
+ ],
190
+ "counts": [{ "component": "stats_bar", "max": 1 }]
191
+ }
192
+ ```
193
+
194
+ **Shared fragments.** Sequence steps match a reference by its fragment id.
195
+ Counts include its rendered component tree once per placement. A structure lock
196
+ also protects that tree: fragment overlays may personalize props but cannot
197
+ swap components or rendered block identities. Fragment edits and overlays
198
+ revalidate referencing pages. Refusals name the source fragment or overlay and
199
+ the page placement (`details.page`, `referencePointer`).
200
+
201
+ **Defaults count when rendered.** Template default fragments fill an absent or
202
+ empty region. They participate in sequence rules, counts, structure locks, and
203
+ component validation just like explicit placements. A populated page region
204
+ replaces its defaults; the unused defaults are not counted.
205
+
206
+ **Nested rules belong to component contracts today.** Templates match the
207
+ region's top-level sequence; they do not define a grammar inside a container's
208
+ props. Give a page type its own contract, for example `blog_body` with
209
+ `content: blockRefArray([featureCardContract, statsBarContract])`, and bind it to
210
+ the existing `ContainerSection` renderer. A blog template step names `blog_body`;
211
+ the compiled component contract limits its nested children. Register that
212
+ contract in both the compile-time contracts list and runtime registry. Compile
213
+ and publish enforce component schemas in addition to template rules; the
214
+ template evaluator itself does not read contracts. Never hand-edit `/patterns/`.
215
+ Nested step grammars remain deferred; use this contract pattern for allowed
216
+ child types, not for arbitrary nested ordering rules.
217
+
218
+ **Overlays keep the page's shape.** A page overlay personalizes a page with
219
+ keyed patches — an object under `regions.<region>` keyed by block `id`
220
+ that changes props:
221
+
222
+ ```json
223
+ {
224
+ "$type": "page-overlay",
225
+ "regions": {
226
+ "main": {
227
+ "personalization-choice": {
228
+ "props": { "title": "Red preference active" }
229
+ }
230
+ }
231
+ }
232
+ }
233
+ ```
234
+
235
+ An overlay may not carry a root `template` key (the binding comes from
236
+ route coverage and the base page; `template/binding-immutable`). Changing a
237
+ block's `component` or replacing a region with an array is legal only when
238
+ the result still satisfies the template — a swap the template does not
239
+ allow is refused naming the overlay and the variant.
240
+
241
+ **Reading a refusal.** Every `template/*` diagnostic carries `documentRef`
242
+ and `jsonPointer` (the overlay that patched the failing block, else the
243
+ page, and the block), and `details` with the governing `template`, its
244
+ `description`, the `step`, `expected` (the tokens the step accepts, as
245
+ `component:<name>` / `fragment:<id>`), `found`, `resolvedPointer` (the
246
+ location in the composed variant), `variant` (the dimension key, empty for
247
+ the base), and `overlays` (every overlay the variant applied, in order). `template/sequence` is a block in
248
+ the wrong place; `template/too-few` is a missing mandatory step;
249
+ `template/count` is a policy bound exceeded; `template/mismatch`,
250
+ `template/unbound`, and `template/not-found` are binding problems.
251
+ `template/locked-changed` means a locked step changed structure: preserve ids
252
+ and use a keyed props patch. `template/locked-missing` identifies a removed
253
+ locked token. `template/base-invalid` requires repairing the base page before
254
+ its variants can pass release validation. Fix the
255
+ page or overlay the diagnostic names, or, with policy authority, loosen the
256
+ template and republish.
87
257
 
88
258
  ### Content Rules
89
259
 
90
260
  - Never hard-code content inside component code. All content lives in JSON.
91
261
  - Migrate copy verbatim. Do not paraphrase, summarize, or invent.
92
262
  - Global elements (header, footer, nav) are CMS-managed fragments in
93
- `.content/fragments/`, rendered via layout fragment defaults. They are
263
+ `.content/fragments/`, rendered via template fragment defaults. They are
94
264
  not hardcoded in framework layout files.
95
265
  - Use `$ref:` prefixes for cross-references between content documents.
96
266
 
@@ -259,7 +429,7 @@ Sites bind their error pages in `site.json` via `errorPages`:
259
429
 
260
430
  - Each binding is a route path resolving to an **ordinary content page**
261
431
  (`.content/pages/404/_index.json`, `.content/pages/error/_index.json`) —
262
- authored with normal layouts and components like any other page.
432
+ authored with normal templates and components like any other page.
263
433
  `notFound` serves unknown routes with HTTP 404; `catchAll` serves
264
434
  application errors (500-class).
265
435
  - Error pages stay out of discovery: set `metadata.sitemap.visible: false`
@@ -486,7 +656,7 @@ framework.
486
656
  #### 5. Add content
487
657
 
488
658
  ```json
489
- // .content/pages/home/_index.json (inside regions.main)
659
+ // .content/pages/_index.json (inside regions.main)
490
660
  {
491
661
  "$type": "component",
492
662
  "id": "features",
@@ -551,39 +721,21 @@ type Props = ContentProps & { className?: string };
551
721
  runtime components — they are the wiring point.
552
722
  4. Use `@gradial/aci` as the SDK package. Do not use `@baremetal/runtime`.
553
723
 
554
- ### Layout Contracts
724
+ ### Template Composition
555
725
 
556
- Layouts define page-level slot structure and optional fragment defaults
557
- for chrome. Use `defineLayoutContract`, `slot()`, and `fragmentDefault()`
558
- from the SDK:
559
-
560
- ```ts
561
- // src/cms/contracts/layouts/index.ts
562
- import { defineLayoutContract, slot, fragmentDefault } from '@gradial/aci';
563
-
564
- export default [
565
- defineLayoutContract({
566
- name: 'default',
567
- slots: [
568
- slot('header'), // filled by fragment default
569
- slot('main', true), // filled by page regions (required)
570
- slot('footer'), // filled by fragment default
571
- ],
572
- defaults: {
573
- header: fragmentDefault('navbar'),
574
- footer: fragmentDefault('site-footer'),
575
- },
576
- }),
577
- ];
578
- ```
726
+ Templates under `.content/templates/<name>.json` define ordered regions,
727
+ placement policy, requiredness, and optional fragment defaults. They are the
728
+ only authored page-composition contract; do not create a TypeScript layout
729
+ contract or add `layoutRegistry` to `.aci.yaml`.
579
730
 
580
731
  Fragment content lives in `.content/fragments/{id}/_index.json` and retains
581
- the resource envelope `"$type": "fragment"` while declaring `component` +
582
- `props`. Chrome components (Header, Footer) are registered in the registry
732
+ the resource envelope `"$type": "fragment"`; its `block` declares
733
+ `"$type": "component"`, `component`, and `props`. Place a shared fragment in
734
+ a page region with `"$fragment:<id>"` or use it as a template default. Chrome components (Header, Footer) are registered in the registry
583
735
  alongside page components.
584
736
 
585
- The `layout` field in page JSON is **required** — there is no implicit
586
- default. Every page must declare which layout it uses.
737
+ Bind each page with `template`, unless route configuration supplies the
738
+ template binding.
587
739
 
588
740
  ## Component Tiers
589
741
 
@@ -693,7 +845,7 @@ slot configuration, nested dot paths, and runtime image rendering rules.
693
845
 
694
846
  Read `references/contracts.md` for the full contract file structure,
695
847
  `defineComponentContract` API, `.aci.yaml` config reference, plain
696
- layout contract objects, and contract barrel patterns.
848
+ template documents and component contract barrel patterns.
697
849
 
698
850
  ## Deep Dive: Design System
699
851
 
@@ -3,6 +3,21 @@
3
3
  The component registry maps contract names to runtime components. It is built
4
4
  using `createRegistry` from `@gradial/aci` with raw `[contract, component]` tuples.
5
5
 
6
+ Every registered component must extend `RootIdentityProps` (exported from
7
+ `@gradial/aci`) and spread the rest of its props onto its root element. The
8
+ renderer passes `data-aci-block` and `data-aci-component` to every block so
9
+ analytics can join events to content; a component that does not declare the
10
+ type fails `createRegistry` typecheck with
11
+ `<id>: props type must extend RootIdentityProps from @gradial/aci`, and one
12
+ that does not spread fails the `rootIdentity` conformance check under
13
+ `npm test`.
14
+
15
+ ```tsx
16
+ export function StatsBar({ stats = [], ...root }: ContentProps & RootIdentityProps) {
17
+ return <section {...root} className="…">…</section>;
18
+ }
19
+ ```
20
+
6
21
  The tuple form provides **type-level validation** — the component must accept
7
22
  at least the props derived from the contract's Zod schema. If you register a
8
23
  component with the wrong prop type, TypeScript will fail at the call site.
@@ -51,7 +66,7 @@ the compiler invokes this route with an isolated render input; hosted CMS
51
66
  traffic is served from the resulting immutable HTML and does not enter Next.
52
67
 
53
68
  Chrome (Header/Footer) is **not** in `layout.tsx`. It is CMS-managed via
54
- layout fragment defaults. The `layout.tsx` file is minimal and includes
69
+ template fragment defaults. The `layout.tsx` file is minimal and includes
55
70
  `<DevRefresh />`:
56
71
 
57
72
  ```tsx
@@ -83,8 +98,9 @@ export const generateMetadata = page.generateMetadata;
83
98
  export default page.default;
84
99
  ```
85
100
 
86
- `createPage` loads the page's layout, resolves all slots (filling from page
87
- regions or layout fragment defaults), and renders blocks via the registry.
101
+ `createPage` loads the compiler's template-derived render envelope, resolves
102
+ all regions (filling from page regions or template fragment defaults), and
103
+ renders blocks via the registry.
88
104
  The `main` slot is wrapped in `<main>`; other slots render as fragments.
89
105
 
90
106
  ## Custom Server Composition
@@ -59,43 +59,23 @@ import { buttonContract } from './button.contract';
59
59
  export default [heroContract, buttonContract];
60
60
  ```
61
61
 
62
- ## Layout Contract API
62
+ ## Template Composition
63
63
 
64
- Use `defineLayoutContract`, `slot()`, and `fragmentDefault()` from the SDK:
65
-
66
- ```ts
67
- // src/cms/contracts/layouts/index.ts
68
- import { defineLayoutContract, slot, fragmentDefault } from '@gradial/aci';
69
-
70
- export default [
71
- defineLayoutContract({
72
- name: 'marketing',
73
- slots: [
74
- slot('header'),
75
- slot('main', true),
76
- slot('footer'),
77
- ],
78
- defaults: {
79
- header: fragmentDefault('site-header'),
80
- footer: fragmentDefault('site-footer'),
81
- },
82
- }),
83
- ];
84
- ```
85
-
86
- Content pages reference layouts by name:
64
+ Templates under `.content/templates/<name>.json` are the sole authored
65
+ page-composition contract. Content pages reference templates by name:
87
66
 
88
67
  ```json
89
68
  {
90
- "layout": "marketing",
69
+ "template": "marketing",
91
70
  "regions": {
92
71
  "main": [...]
93
72
  }
94
73
  }
95
74
  ```
96
75
 
97
- The `header` and `footer` slots are filled automatically by fragment defaults
98
- when the page doesn't provide blocks for them.
76
+ Templates define ordered regions, placement policy, requiredness, and defaults.
77
+ The `header` and `footer` regions can be filled automatically by fragment
78
+ defaults when the page does not provide blocks for them.
99
79
 
100
80
  ## Multiple Runtime Mappings
101
81
 
@@ -119,7 +99,6 @@ source:
119
99
  publicDir: "public" # Astro only
120
100
 
121
101
  componentRegistry: ./src/cms/contracts/components/index.ts
122
- layoutRegistry: ./src/cms/contracts/layouts/index.ts
123
102
 
124
103
  # Optional for custom renderers; Astro/Next use framework conventions.
125
104
  # rendererEntry: ./src/cms/renderer.ts
@@ -145,7 +124,6 @@ rendererProtocol: stdio-json
145
124
  | `source.outDir` | Build output directory (Astro) |
146
125
  | `source.publicDir` | Public assets directory (Astro) |
147
126
  | `componentRegistry` | Path to component contract barrel |
148
- | `layoutRegistry` | Path to layout contract barrel |
149
127
  | `rendererEntry` | Optional renderer entry override; omit for framework conventions |
150
128
  | `routes.cmsManaged` | Catch-all route pattern for CMS pages |
151
129
  | `rendererProtocol` | Renderer communication protocol |
@@ -159,7 +137,8 @@ rendererProtocol: stdio-json
159
137
 
160
138
  ### Rules
161
139
 
162
- - `componentRegistry` and `layoutRegistry` point to contract-only modules.
140
+ - `componentRegistry` points to a contract-only module. Do not add
141
+ `layoutRegistry`; page composition lives in `.content/templates`.
163
142
  - No secrets in `.aci.yaml`, docs, fixtures, or generated files.
164
143
  - The `cmsManaged` route pattern should match the filesystem: use `/[[...slug]]`
165
144
  (optional catch-all) for Next.js repos so local `/` arrives with an empty
@@ -27,8 +27,11 @@ tsconfig.json TypeScript config
27
27
  home/_index.json Homepage (route /)
28
28
  about/_index.json About page (route /about) (starter content only)
29
29
  fragments/
30
- navbar/_index.json Navbar fragment (layout default)
31
- site-footer/_index.json Footer fragment (layout default)
30
+ navbar/_index.json Navbar fragment (template default)
31
+ site-footer/_index.json Footer fragment (template default)
32
+ templates/
33
+ home.json Page composition contract
34
+ standard.json Page composition contract
32
35
  src/
33
36
  cms/
34
37
  contracts/
@@ -42,8 +45,6 @@ src/
42
45
  siteNavigation.contract.ts Site navigation contract (starter content only)
43
46
  siteFooter.contract.ts Site footer contract (starter content only)
44
47
  index.ts Component contract barrel
45
- layouts/
46
- index.ts Layout contract barrel (plain objects)
47
48
  registry.ts Component registry — createRegistry([contract, component] tuples)
48
49
  components/
49
50
  elements/ Small reusable components + atomic content blocks
@@ -30,8 +30,8 @@ export const generateMetadata = page.generateMetadata;
30
30
  export default page.default;
31
31
  ```
32
32
 
33
- `createPage` loads the page's layout, resolves all slots (filling from
34
- page regions or layout fragment defaults), and renders blocks via the
33
+ `createPage` loads the compiler's template-derived render envelope, resolves
34
+ all regions (filling from page regions or template fragment defaults), and renders blocks via the
35
35
  registry. The `main` slot is wrapped in `<main>`; other slots render as
36
36
  fragments.
37
37
 
@@ -177,7 +177,7 @@ and shared across all releases.
177
177
 
178
178
  The root layout is minimal: import CSS, optionally load site config, and
179
179
  include `<DevRefresh />`. Chrome (Header/Footer) is **not** in layout.tsx —
180
- it is CMS-managed via layout fragment defaults and rendered by `createPage`.
180
+ it is CMS-managed via template fragment defaults and rendered by `createPage`.
181
181
 
182
182
  ```tsx
183
183
  import { DevRefresh, getSiteConfig } from '@gradial/aci/next';