@gradial/aci 0.1.30 → 0.1.33

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 (164) hide show
  1. package/README.md +4 -4
  2. package/bin/aci-darwin-arm64 +0 -0
  3. package/bin/aci.js +33 -32
  4. package/dist/astro/assets.d.ts +46 -0
  5. package/dist/astro/assets.js +51 -0
  6. package/dist/astro/index.d.ts +2 -0
  7. package/dist/astro/index.js +8 -0
  8. package/dist/astro/page.d.ts +88 -0
  9. package/dist/astro/page.js +117 -0
  10. package/dist/content/provider.d.ts +7 -0
  11. package/dist/dev/content-watch.d.ts +1 -0
  12. package/dist/dev/content-watch.js +1 -1
  13. package/dist/next/config.d.ts +6 -0
  14. package/dist/next/config.js +20 -13
  15. package/dist/next/content-watch.d.ts +1 -0
  16. package/dist/next/content-watch.js +2 -2
  17. package/dist/next/index.d.ts +1 -0
  18. package/dist/next/index.js +1 -0
  19. package/dist/next/middleware.d.ts +3 -2
  20. package/dist/next/middleware.js +40 -7
  21. package/dist/next/page.d.ts +13 -5
  22. package/dist/next/page.js +42 -17
  23. package/dist/next/server.d.ts +1 -1
  24. package/dist/next/server.js +33 -9
  25. package/dist/providers/s3.d.ts +7 -0
  26. package/dist/providers/s3.js +20 -0
  27. package/dist/testing/conformance/checks.d.ts +12 -0
  28. package/dist/testing/conformance/checks.js +483 -0
  29. package/dist/testing/conformance/index.d.ts +3 -0
  30. package/dist/testing/conformance/index.js +2 -0
  31. package/dist/testing/conformance/preset.d.ts +14 -0
  32. package/dist/testing/conformance/preset.js +98 -0
  33. package/dist/testing/conformance/types.d.ts +45 -0
  34. package/dist/testing/conformance/types.js +7 -0
  35. package/package.json +17 -9
  36. package/src/cli/materialize-template.mjs +133 -0
  37. package/src/cli/validate-content.mjs +0 -0
  38. package/src/cli/verify-next-cdn-routes.mjs +10 -19
  39. package/templates/astro/starter-content/src/cms/contracts/components/button.contract.ts +19 -0
  40. package/templates/astro/starter-content/src/cms/contracts/components/container.contract.ts +19 -0
  41. package/templates/astro/starter-content/src/cms/contracts/components/featureCard.contract.ts +15 -0
  42. package/templates/astro/starter-content/src/cms/contracts/components/featureGrid.contract.ts +16 -0
  43. package/templates/astro/starter-content/src/cms/contracts/components/homeHero.contract.ts +36 -0
  44. package/templates/astro/starter-content/src/cms/contracts/components/index.ts +21 -0
  45. package/templates/astro/starter-content/src/cms/contracts/components/statsBar.contract.ts +19 -0
  46. package/templates/astro/starter-content/src/cms/registry.ts +28 -0
  47. package/templates/astro/template/{.aci.yaml → .aci.yaml.tmpl} +1 -1
  48. package/templates/astro/template/README.md.tmpl +53 -0
  49. package/templates/astro/template/gitignore +12 -0
  50. package/templates/astro/template/package.json.tmpl +5 -3
  51. package/templates/astro/template/src/cms/contracts/components/index.ts +3 -0
  52. package/templates/astro/template/src/cms/registry.ts +3 -26
  53. package/templates/astro/template/src/pages/[...slug].astro +13 -35
  54. package/templates/astro/template/tests/conformance.test.ts.tmpl +12 -0
  55. package/templates/common/.agents/AGENTS.md +3 -5
  56. package/templates/common/.agents/skills/aci/SKILL.md +72 -23
  57. package/templates/common/.agents/skills/authoring/SKILL.md +53 -14
  58. package/templates/common/.agents/skills/authoring/references/block-registry.md +109 -15
  59. package/templates/common/.agents/skills/authoring/references/contracts.md +23 -7
  60. package/templates/common/.agents/skills/authoring/references/repo-structure.md +35 -19
  61. package/templates/common/.agents/skills/authoring/references/sdk-server-functions.md +75 -17
  62. package/templates/common/.agents/skills/migration/SKILL.md +7 -1
  63. package/templates/common/.agents/skills/migration/references/canon-migration.md +471 -0
  64. package/templates/common/.content/config/site.json.tmpl +15 -0
  65. package/templates/common/.content/pages/home/_index.json +4 -103
  66. package/templates/common/.nvmrc +1 -0
  67. package/templates/common/README.md.tmpl +32 -11
  68. package/templates/common/contracts/components/index.ts +1 -19
  69. package/templates/common/design-system/tokens/colors.css +34 -10
  70. package/templates/common/starter-content/.content/config/robots.txt +2 -0
  71. package/templates/common/starter-content/.content/pages/home/_index.json +112 -0
  72. package/templates/common/starter-content/.content/schemas/site.schema.json +138 -0
  73. package/templates/nextjs/starter-content/src/cms/contracts/components/container.contract.ts +19 -0
  74. package/templates/nextjs/starter-content/src/cms/contracts/components/featureCard.contract.ts +15 -0
  75. package/templates/nextjs/starter-content/src/cms/contracts/components/featureGrid.contract.ts +16 -0
  76. package/templates/nextjs/starter-content/src/cms/contracts/components/index.ts +21 -0
  77. package/templates/nextjs/starter-content/src/cms/contracts/components/siteFooter.contract.ts +45 -0
  78. package/templates/nextjs/starter-content/src/cms/contracts/components/siteNavigation.contract.ts +30 -0
  79. package/templates/nextjs/starter-content/src/cms/contracts/components/statsBar.contract.ts +19 -0
  80. package/templates/nextjs/starter-content/src/cms/registry.ts +28 -0
  81. package/templates/nextjs/template/{.aci.yaml → .aci.yaml.tmpl} +1 -1
  82. package/templates/nextjs/template/README.md.tmpl +52 -0
  83. package/templates/nextjs/template/gitignore +15 -0
  84. package/templates/nextjs/template/next.config.mjs +1 -6
  85. package/templates/nextjs/template/package.json.tmpl +5 -5
  86. package/templates/nextjs/template/src/app/gradial/assets/[...path]/route.ts +1 -4
  87. package/templates/nextjs/template/src/app/layout.tsx +1 -3
  88. package/templates/nextjs/template/src/cms/contracts/components/index.ts +1 -13
  89. package/templates/nextjs/template/src/cms/registry.ts +3 -26
  90. package/templates/nextjs/template/src/design-system/styles.css +346 -0
  91. package/templates/nextjs/template/src/design-system/tokens/colors.css +110 -0
  92. package/templates/nextjs/template/src/design-system/tokens/components.css +86 -0
  93. package/templates/nextjs/template/src/design-system/tokens/index.css +6 -0
  94. package/templates/nextjs/template/src/design-system/tokens/radii.css +5 -0
  95. package/templates/nextjs/template/src/design-system/tokens/shadows.css +9 -0
  96. package/templates/nextjs/template/src/design-system/tokens/spacing.css +26 -0
  97. package/templates/nextjs/template/src/design-system/tokens/typography.css +32 -0
  98. package/templates/nextjs/template/src/middleware.ts.tmpl +12 -0
  99. package/templates/nextjs/template/tests/conformance.test.ts.tmpl +12 -0
  100. package/templates/nextjs/template/src/app/_components/ConsentManager.tsx +0 -132
  101. package/templates/nextjs/template/src/cms/contracts/components/container.contract.ts +0 -20
  102. package/templates/nextjs/template/src/middleware.ts +0 -7
  103. /package/templates/{nextjs/template → astro/starter-content}/src/cms/contracts/components/siteFooter.contract.ts +0 -0
  104. /package/templates/{nextjs/template → astro/starter-content}/src/cms/contracts/components/siteNavigation.contract.ts +0 -0
  105. /package/templates/astro/{template → starter-content}/src/components/chrome/Footer.astro +0 -0
  106. /package/templates/astro/{template → starter-content}/src/components/chrome/Header.astro +0 -0
  107. /package/templates/astro/{template → starter-content}/src/components/elements/AtomicBlock.astro +0 -0
  108. /package/templates/astro/{template → starter-content}/src/components/elements/AtomicBlocks.astro +0 -0
  109. /package/templates/astro/{template → starter-content}/src/components/elements/Button.astro +0 -0
  110. /package/templates/astro/{template → starter-content}/src/components/elements/ButtonBlock.astro +0 -0
  111. /package/templates/astro/{template → starter-content}/src/components/elements/Container.astro +0 -0
  112. /package/templates/astro/{template → starter-content}/src/components/elements/Icon.astro +0 -0
  113. /package/templates/astro/{template → starter-content}/src/components/elements/Logo.astro +0 -0
  114. /package/templates/astro/{template → starter-content}/src/components/elements/RemoteImage.astro +0 -0
  115. /package/templates/astro/{template → starter-content}/src/components/sections/ContainerSection.astro +0 -0
  116. /package/templates/astro/{template → starter-content}/src/components/sections/FeatureCard.astro +0 -0
  117. /package/templates/astro/{template → starter-content}/src/components/sections/FeatureGrid.astro +0 -0
  118. /package/templates/astro/{template → starter-content}/src/components/sections/HomeHero.astro +0 -0
  119. /package/templates/astro/{template → starter-content}/src/components/sections/StatsBar.astro +0 -0
  120. /package/templates/astro/{template → starter-content}/src/fixtures/atomicBlocks.ts +0 -0
  121. /package/templates/astro/{template → starter-content}/src/fixtures/composition.ts +0 -0
  122. /package/templates/astro/{template → starter-content}/src/fixtures/index.ts +0 -0
  123. /package/templates/astro/{template → starter-content}/src/fixtures/sections.ts +0 -0
  124. /package/templates/astro/{template → starter-content}/src/render/RenderPage.astro +0 -0
  125. /package/templates/astro/{template → starter-content}/src/stories/CompositionStoryFrame.astro +0 -0
  126. /package/templates/astro/{template → starter-content}/src/stories/HomeHero.stories.ts +0 -0
  127. /package/templates/astro/{template → starter-content}/src/stories/SectionStoryFrame.astro +0 -0
  128. /package/templates/astro/{template → starter-content}/src/stories/StoryDocFrame.astro +0 -0
  129. /package/templates/common/{.content → starter-content/.content}/.gradial-dam/blobs/sha256/7f/bf/7fbfc7fe230ff12688c496f660119e41d9725c4504d82e310c15665c73c09292 +0 -0
  130. /package/templates/common/{.content → starter-content/.content}/.gradial-dam/blobs/sha256/a0/f5/a0f506689de9b15a7055f70103a3f02cd3ba105771ae2de6754df2ede1210d3a +0 -0
  131. /package/templates/common/{.content → starter-content/.content}/.gradial-dam/blobs/sha256/dd/b7/ddb7e9642be08317bcd4b2bd26b58ba8d0d34dd7239e03e684a4f3634fa3845b +0 -0
  132. /package/templates/common/{.content → starter-content/.content}/.gradial-dam/blobs/sha256/fb/32/fb3260e8286ea8231ea1c77b9c0be92d6edd9026565731eb14cfdcfd6b1b75b3 +0 -0
  133. /package/templates/common/{.content → starter-content/.content}/config/site.json +0 -0
  134. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_about_hero/asset.json +0 -0
  135. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_about_hero/editorial.json +0 -0
  136. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_about_hero/governance.json +0 -0
  137. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_about_hero/versions/v0001.json +0 -0
  138. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_home_hero/asset.json +0 -0
  139. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_home_hero/editorial.json +0 -0
  140. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_home_hero/governance.json +0 -0
  141. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_home_hero/versions/v0001.json +0 -0
  142. /package/templates/common/{.content → starter-content/.content}/fragments/navbar/_index.json +0 -0
  143. /package/templates/common/{.content → starter-content/.content}/fragments/site-footer/_index.json +0 -0
  144. /package/templates/common/{.content → starter-content/.content}/pages/about/_index.json +0 -0
  145. /package/templates/common/{public → starter-content/public}/assets/Dwire-model.png +0 -0
  146. /package/templates/common/{public → starter-content/public}/assets/seattle.avif +0 -0
  147. /package/templates/nextjs/{template → starter-content}/src/cms/contracts/components/button.contract.ts +0 -0
  148. /package/templates/nextjs/{template → starter-content}/src/cms/contracts/components/homeHero.contract.ts +0 -0
  149. /package/templates/nextjs/{template → starter-content}/src/components/chrome/Footer.tsx +0 -0
  150. /package/templates/nextjs/{template → starter-content}/src/components/chrome/Header.tsx +0 -0
  151. /package/templates/nextjs/{template → starter-content}/src/components/elements/AtomicBlock.tsx +0 -0
  152. /package/templates/nextjs/{template → starter-content}/src/components/elements/Button.tsx +0 -0
  153. /package/templates/nextjs/{template → starter-content}/src/components/elements/ButtonBlock.tsx +0 -0
  154. /package/templates/nextjs/{template → starter-content}/src/components/elements/Container.tsx +0 -0
  155. /package/templates/nextjs/{template → starter-content}/src/components/elements/Icon.tsx +0 -0
  156. /package/templates/nextjs/{template → starter-content}/src/components/elements/Logo.tsx +0 -0
  157. /package/templates/nextjs/{template → starter-content}/src/components/sections/ContainerSection.tsx +0 -0
  158. /package/templates/nextjs/{template → starter-content}/src/components/sections/FeatureCard.tsx +0 -0
  159. /package/templates/nextjs/{template → starter-content}/src/components/sections/FeatureGrid.tsx +0 -0
  160. /package/templates/nextjs/{template → starter-content}/src/components/sections/HomeHero.tsx +0 -0
  161. /package/templates/nextjs/{template → starter-content}/src/components/sections/StatsBar.tsx +0 -0
  162. /package/templates/nextjs/{template → starter-content}/src/stories/Button.stories.tsx +0 -0
  163. /package/templates/nextjs/{template → starter-content}/src/stories/Header.stories.tsx +0 -0
  164. /package/templates/nextjs/{template → starter-content}/src/stories/HomeHero.stories.tsx +0 -0
@@ -5,6 +5,31 @@ description: Onboarding, running builds, validating, and troubleshooting ACI sit
5
5
 
6
6
  # ACI — Onboarding and Troubleshooting
7
7
 
8
+ ## Creating a New Site
9
+
10
+ ```bash
11
+ # Scaffold-only (default) — golden-path skeleton, no demo content
12
+ # Astro is standard for new customer sites
13
+ aci init my-site --template astro-starter
14
+
15
+ # Next.js is for specific SSR properties
16
+ aci init my-site --template next-starter
17
+
18
+ # With demo content, components, and stories
19
+ aci init my-site --template astro-starter --with-starter-content
20
+
21
+ # List available templates
22
+ aci init --list
23
+ ```
24
+
25
+ The **scaffold** is the new-customer entry point: config, framework wiring,
26
+ page route, empty registry, design system, and agent skills. Add your own
27
+ contracts and components from here.
28
+
29
+ The **--with-starter-content** flag overlays demo material on top of the
30
+ scaffold: `.content/` fixtures, demo components (sections, elements, chrome),
31
+ a full registry, stories, and fixtures.
32
+
8
33
  ## How ACI Works
9
34
 
10
35
  ```
@@ -41,8 +66,11 @@ Each framework uses a packaged SDK adapter for ACI dev/build behavior:
41
66
  - **Next.js**: `withAci()` in `next.config.ts`
42
67
 
43
68
  These adapters own canonical ACI behavior: content watch/reload, local DAM
44
- derivative serving at `/.gradial-dam/*`, and asset path rewrites. Do not
45
- copy site-local middleware for these concerns.
69
+ derivative serving at `/.gradial-dam/*`, asset path rewrites, output mode,
70
+ outputFileTracingRoot, base-path/build-mode/dist-dir env injection, CDN
71
+ headers, and content-watch plugin. Repos should pass an empty `withAci()`
72
+ call and rely on SDK defaults. Use `passthroughRoutes` only for
73
+ site-specific non-CMS routes.
46
74
 
47
75
  Only Next.js and Astro are supported. SvelteKit support has been removed.
48
76
 
@@ -52,14 +80,11 @@ All commands run from the site directory (where `.aci.yaml` lives).
52
80
 
53
81
  | Command | Purpose |
54
82
  |---------|---------|
55
- | `npm run aci:compile` | Compile contract registry. Run after changing contracts. |
56
- | `npm run content:compile` | Compile content from `.content/` into `.aci/compiled/`. |
57
- | `npm run aci:validate` | Validate content against schemas without building. |
58
- | `npm run aci:doctor` | Check project health: config, content, schemas, routes. |
59
- | `npm run aci:build` | Full build: contracts + content + framework. |
83
+ | `npm run dev` | Start dev server with content hot-reload (auto-compiles content). |
84
+ | `npm run build` | Framework production build. |
60
85
  | `npm run typecheck` | TypeScript type checking. |
61
- | `npm run build` | Framework build. |
62
- | `npm run dev` | Start dev server with content hot-reload. |
86
+ | `npm test` | Run conformance tests. |
87
+ | `npm run content:compile` | Compile content from `.content/` into `.aci/compiled/`. Standard: `aci content compile --content .content --out .aci/compiled` |
63
88
 
64
89
  ## Development Workflow
65
90
 
@@ -67,39 +92,63 @@ All commands run from the site directory (where `.aci.yaml` lives).
67
92
 
68
93
  ```bash
69
94
  npm install
70
- npm run aci:compile
71
95
  npm run dev
72
96
  ```
73
97
 
98
+ The dev server compiles content on startup — no manual compile needed.
99
+
74
100
  The dev server watches for content changes and re-compiles automatically
75
101
  via the framework's ACI adapter. Edit `.content/` files and see changes
76
- in the browser.
102
+ in the browser. No manual compile step needed before `dev`.
77
103
 
78
104
  ### Full local build
79
105
 
80
106
  ```bash
81
- npm run aci:compile
82
107
  npm run content:compile
83
108
  npm run typecheck
84
- npm run build
85
109
  ```
86
110
 
87
111
  ### Content editing loop
88
112
 
89
113
  1. Edit `.content/pages/{slug}/_index.json` or `.content/config/site.json`.
90
- 2. Run `npm run aci:validate` to check content is valid.
91
- 3. Run `npm run content:compile` to compile content.
92
- 4. Run `npm run build` to build the site.
93
- 5. Check the output for your changes.
114
+ 2. Run `npm run content:compile` to compile content.
115
+ 3. Run `npm run build` to build the site.
116
+ 4. Check the output for your changes.
94
117
 
95
118
  ## Test Order (Fastest to Slowest)
96
119
 
97
- 1. `npm run aci:validate` — content validation
98
- 2. `npm run aci:compile` — contract compile
99
- 3. `npm run typecheck` — TypeScript
100
- 4. `npm run content:compile` — content compile
101
- 5. `npm run build` — framework build
102
- 6. `npm run build:storybook` — Storybook (if configured)
120
+ 1. `npm run typecheck` — TypeScript
121
+ 2. `npm test` — conformance tests
122
+ 3. `npm run content:compile` — content compile
123
+ 4. `npm run build` — framework build
124
+ 5. `npm run build:storybook` — Storybook (if configured)
125
+
126
+ ## Conformance Checks
127
+
128
+ Repos run conformance tests in CI using the `@gradial/aci/testing` preset:
129
+
130
+ ```ts
131
+ import { conformancePreset } from '@gradial/aci/testing';
132
+
133
+ conformancePreset({
134
+ framework: 'next',
135
+ siteId: 'your-tenant-id',
136
+ });
137
+ ```
138
+
139
+ Default checks validate:
140
+ - `config` -- `withAci()` present in config
141
+ - `registry` -- `createRegistry()` usage (no hand-rolled maps)
142
+ - `pageEntry` -- `createPage(registry)` usage in catch-all route
143
+ - `middleware` -- `createGradialMiddleware({ siteId })` with correct siteId
144
+ - `noFileContentProvider` -- no direct `FileContentProvider` imports in app code
145
+ - `noCustomContent` -- no `src/lib/content.ts` shim
146
+ - `noLegacyAssetRoute` -- no `[releaseId]` segment in asset route path
147
+ - `sdkVersion` -- SDK version compatibility
148
+
149
+ Optional (disabled by default):
150
+ - `buildSucceeds` -- full build completes
151
+ - `contentCompile` -- content compilation succeeds
103
152
 
104
153
  ## Troubleshooting
105
154
 
@@ -12,6 +12,7 @@ description: Author content and build components for ACI sites. Use when creatin
12
12
  | Path | Purpose |
13
13
  |------|---------|
14
14
  | `.content/config/site.json` | Site metadata, SEO defaults |
15
+ | `.content/config/robots.txt` | Authored crawler rules; publish bundles this file verbatim as `assets/robots.txt` when present |
15
16
  | `.content/pages/{slug}/_index.json` | Page content (slug maps to route) |
16
17
  | `.content/fragments/{id}/_index.json` | Shared fragments (navbar, footer — used by layout defaults) |
17
18
  | `.aci/schemas/page.schema.json` | Page validation schema (auto-generated from contracts) |
@@ -28,7 +29,8 @@ Every page JSON requires:
28
29
  "layout": "default",
29
30
  "metadata": {
30
31
  "title": "Page Title",
31
- "description": "Meta description"
32
+ "description": "Meta description",
33
+ "sitemap": { "visible": true }
32
34
  },
33
35
  "regions": {
34
36
  "main": []
@@ -36,6 +38,35 @@ Every page JSON requires:
36
38
  }
37
39
  ```
38
40
 
41
+ Base page fields are SDK-owned and included in the generated
42
+ `.aci/schemas/page.schema.json`.
43
+
44
+ #### Sitemap visibility
45
+
46
+ `metadata.sitemap.visible` controls whether the page appears in the generated
47
+ `sitemap.xml` release artifact:
48
+
49
+ - **Omit or `true`** (default) — the page is included in the sitemap when other
50
+ filters allow it (published status, robots.txt rules).
51
+ - **`false`** — the page is excluded from the sitemap. The page still renders
52
+ normally; only its sitemap presence changes.
53
+
54
+ ```json
55
+ {
56
+ "metadata": {
57
+ "title": "Private Campaign Page",
58
+ "sitemap": { "visible": false }
59
+ }
60
+ }
61
+ ```
62
+
63
+ Use `false` for pages that should publish but stay out of search engine
64
+ discovery: temporary campaign pages, event-specific landing pages, staging
65
+ pages, or pages already blocked by `robots.txt` (setting it explicitly makes
66
+ the intent clear in content).
67
+
68
+ This does **not** add a `noindex` meta tag — it only controls sitemap inclusion.
69
+
39
70
  The `layout` field is required — it determines which slots the page can
40
71
  fill. The `default` layout provides `header`/`main`/`footer` slots with
41
72
  fragment defaults for chrome (navbar, footer). Pages typically only fill
@@ -400,8 +431,7 @@ framework.
400
431
  #### 6. Verify
401
432
 
402
433
  ```bash
403
- npm run aci:compile # Contract compiles
404
- npm run aci:validate # Content validates
434
+ npm run aci:compile # Contracts + content compile
405
435
  npm run typecheck # Types check
406
436
  npm run build # Framework builds
407
437
  ```
@@ -446,24 +476,26 @@ type Props = ContentProps & { className?: string };
446
476
  ### Layout Contracts
447
477
 
448
478
  Layouts define page-level slot structure and optional fragment defaults
449
- for chrome. Fragment defaults let CMS-managed fragments (navbar, footer)
450
- fill slots automatically when the page doesn't provide blocks for them:
479
+ for chrome. Use `defineLayoutContract`, `slot()`, and `fragmentDefault()`
480
+ from the SDK:
451
481
 
452
482
  ```ts
453
483
  // src/cms/contracts/layouts/index.ts
484
+ import { defineLayoutContract, slot, fragmentDefault } from '@gradial/aci';
485
+
454
486
  export default [
455
- {
487
+ defineLayoutContract({
456
488
  name: 'default',
457
489
  slots: [
458
- { name: 'header', required: false }, // filled by fragment default
459
- { name: 'main', required: true }, // filled by page regions
460
- { name: 'footer', required: false }, // filled by fragment default
490
+ slot('header'), // filled by fragment default
491
+ slot('main', true), // filled by page regions (required)
492
+ slot('footer'), // filled by fragment default
461
493
  ],
462
494
  defaults: {
463
- header: [{ kind: 'fragment-ref', fragmentId: 'navbar', inline: true }],
464
- footer: [{ kind: 'fragment-ref', fragmentId: 'site-footer', inline: true }],
495
+ header: fragmentDefault('navbar'),
496
+ footer: fragmentDefault('site-footer'),
465
497
  },
466
- },
498
+ }),
467
499
  ];
468
500
  ```
469
501
 
@@ -553,14 +585,21 @@ Three theme classes control semantic token resolution:
553
585
 
554
586
  ## Storybook
555
587
 
556
- Every ACI frontend repo should have Storybook configured. Stories live
557
- under `src/stories/` and follow the framework's Storybook setup.
588
+ Every ACI frontend repo should have Storybook configured. Use **Storybook 10+**
589
+ with the framework-specific adapter:
590
+
591
+ - **Next.js**: `@storybook/nextjs-vite`
592
+ - **Astro**: `@storybook-astro/framework`
593
+
594
+ Stories live under `src/stories/` and follow the framework's Storybook setup.
558
595
 
559
596
  ```bash
560
597
  npm run storybook # Dev mode
561
598
  npm run build:storybook # Build all stories
562
599
  ```
563
600
 
601
+ All repos should converge on Storybook 10. Repos on Storybook 9 should upgrade.
602
+
564
603
  ## Deep Dive: Component Registry
565
604
 
566
605
  Read `references/block-registry.md` for the `createRegistry` pattern,
@@ -45,18 +45,31 @@ The SDK provides `createPage(registry)` which owns param normalization, content
45
45
  loading, layout resolution, metadata generation, notFound handling, slot
46
46
  resolution (including fragment defaults), and block rendering.
47
47
 
48
+ Path shape: `/__r/{releaseId}/routes/{...slug}`. The SDK extracts releaseId
49
+ internally; middleware rewrites public URLs to `/__r/{releaseId}/routes/{path}`
50
+ for the app-level route. The `routes` namespace leaves room for future siblings
51
+ under the release prefix. The filesystem uses an **optional catch-all** `[[...slug]]`
52
+ (not required `[...slug]`). Production middleware always rewrites to
53
+ `/__r/{releaseId}/routes/{path}`, but file-provider local dev passes requests
54
+ unrewritten so `/` arrives with an empty slug.
55
+
48
56
  Chrome (Header/Footer) is **not** in `layout.tsx`. It is CMS-managed via
49
- layout fragment defaults. The `layout.tsx` file is minimal:
57
+ layout fragment defaults. The `layout.tsx` file is minimal and includes
58
+ `<DevRefresh />`:
50
59
 
51
60
  ```tsx
52
61
  // src/app/layout.tsx — minimal, no chrome
53
62
  import type { ReactNode } from 'react';
63
+ import { DevRefresh } from '@gradial/aci/next';
54
64
  import '@/design-system/styles.css';
55
65
 
56
66
  export default function Layout({ children }: { children: ReactNode }) {
57
67
  return (
58
68
  <html lang="en">
59
- <body>{children}</body>
69
+ <body>
70
+ {children}
71
+ <DevRefresh />
72
+ </body>
60
73
  </html>
61
74
  );
62
75
  }
@@ -95,23 +108,83 @@ an app-owned HTTP endpoint backed by a server-side `ContentProvider`.
95
108
 
96
109
  ## Astro Pattern
97
110
 
98
- Astro uses a catch-all route and renders via the shared registry:
111
+ The SDK provides `getPageData(Astro)` as the Astro equivalent of Next.js
112
+ `createPage`. It encapsulates route normalization, content resolution (preview
113
+ render input vs FileContentProvider), metadata derivation, layout resolution,
114
+ slot filling, and collect-tag emission into a single call.
99
115
 
100
116
  ```astro
101
117
  ---
102
118
  // src/pages/[...slug].astro
103
- import { createPage } from '@gradial/aci/astro/page';
104
- import { registry } from '@/cms/registry';
119
+ import { getGradialStaticPaths, getPageData, createRenderChildren } from '@gradial/aci/astro';
120
+ import { registry } from '../cms/registry';
121
+ import SiteLayout from '../layouts/SiteLayout.astro';
105
122
 
106
- const page = createPage(registry);
107
- export const getStaticPaths = page.getStaticPaths;
108
- export default page.default;
123
+ export const getStaticPaths = getGradialStaticPaths;
124
+
125
+ const data = await getPageData(Astro);
126
+ const renderChildren = createRenderChildren(registry);
109
127
  ---
128
+
129
+ <SiteLayout
130
+ title={data.meta.title}
131
+ description={data.meta.description}
132
+ canonical={data.meta.canonical}
133
+ siteName={data.meta.siteName}
134
+ collectTag={data.collectTag}
135
+ >
136
+ {data.layout.slots.map((slot) => {
137
+ const blocks = data.slots[slot.name];
138
+ if (!blocks?.length) return null;
139
+ const rendered = renderChildren(blocks);
140
+ if (slot.name === 'main') {
141
+ return (
142
+ <main id="content-main">
143
+ {rendered.map((child) => {
144
+ const Comp = child.Component;
145
+ return Comp ? <Comp {...child.props} /> : null;
146
+ })}
147
+ </main>
148
+ );
149
+ }
150
+ return rendered.map((child) => {
151
+ const Comp = child.Component;
152
+ return Comp ? <Comp {...child.props} /> : null;
153
+ });
154
+ })}
155
+ </SiteLayout>
110
156
  ```
111
157
 
112
- The `createPage` helper owns param normalization, content loading, layout
113
- resolution, metadata generation, notFound handling, slot resolution
114
- (including fragment defaults), and block rendering.
158
+ `getPageData()` returns a `PageData` object with `{ route, siteConfig, page,
159
+ meta, layout, slots, collectTag }`. The page template receives typed data
160
+ and only needs to iterate slots and render blocks via the registry.
161
+
162
+ For DAM asset URLs, use `damAssetUrl()`:
163
+
164
+ ```astro
165
+ ---
166
+ import { damAssetUrl } from '@gradial/aci/astro';
167
+ ---
168
+ <img src={damAssetUrl('images/hero.webp')} alt="Hero" />
169
+ ```
170
+
171
+ ### Astro Asset Serving
172
+
173
+ **DAM assets are content-addressed and globally shared.**
174
+
175
+ DAM derivative assets (processed images, optimized media) are staged ONCE at the
176
+ hosting bucket's global `.gradial-dam/` location. The content-addressed filenames
177
+ contain hashes, so each unique asset is written only once and shared across all
178
+ releases.
179
+
180
+ The CloudFront function rewrites `/.gradial-dam/*` requests to the global
181
+ `{keyPrefix}/.gradial-dam/*` S3 path with no release lookup. These assets are
182
+ served with immutable caching (`max-age=31536000`) because the filenames contain
183
+ content hashes.
184
+
185
+ No separate CloudFront origin behavior is needed — the existing CloudFront
186
+ function in `infra/modules/hosting-cloudfront-site/function.js.tftpl` handles
187
+ the rewrite automatically.
115
188
 
116
189
  ### Astro-Specific Paths
117
190
 
@@ -119,26 +192,47 @@ resolution, metadata generation, notFound handling, slot resolution
119
192
  |---------|------|
120
193
  | Routes | `src/pages/[...slug].astro` |
121
194
  | Layout | `src/layouts/SiteLayout.astro` |
122
- | Config | `astro.config.mjs` with `withAci()` |
195
+ | Config | `astro.config.mjs` with bare `withAci()` |
123
196
  | Registry | `src/cms/registry.ts` (shared) |
124
197
  | Storybook | `.storybook/main.ts`, `.storybook/preview.ts` |
198
+ | Assets | CloudFront function rewrite (automatic, no infrastructure changes needed) |
125
199
 
126
200
  ## Middleware
127
201
 
202
+ Middleware requires explicit `siteId`. The SDK exports a canonical matcher that
203
+ repos re-export:
204
+
128
205
  ```ts
129
206
  // src/middleware.ts
130
- import { createMiddleware } from '@gradial/aci/next/middleware';
131
- export default createMiddleware();
207
+ import { createGradialMiddleware } from '@gradial/aci/next/middleware';
132
208
  export { config } from '@gradial/aci/next/middleware';
209
+ export default createGradialMiddleware({ siteId: 'your-tenant-id' });
133
210
  ```
134
211
 
212
+ The middleware rewrites public URLs to `/__r/{releaseId}/routes/{path}` for
213
+ the app-level route. The SDK extracts releaseId internally.
214
+
135
215
  ## Asset Routes
136
216
 
217
+ **Preferred: NO app-level asset route.** Assets are served via URL rewrite
218
+ (Next.js `withAci()`) or CloudFront function rewrite (Astro/CloudFront) directly
219
+ to S3.
220
+
221
+ DAM derivative assets are content-addressed and globally shared — staged once at
222
+ `{keyPrefix}/.gradial-dam/` and shared across all releases. The SDK's `withAci()`
223
+ already injects a `/.gradial-dam/:path*` rewrite. For CloudFront, the existing
224
+ CloudFront function handles the rewrite automatically.
225
+
226
+ For transitional compatibility, the SDK still exports an asset route handler:
227
+
137
228
  ```ts
138
- // src/app/gradial/assets/[releaseId]/[...path]/route.ts
229
+ // src/app/gradial/assets/[...path]/route.ts (transitional)
139
230
  export { GET, runtime, dynamic } from '@gradial/aci/next/asset-route';
140
231
  ```
141
232
 
233
+ **Note**: Asset URLs do NOT include `[releaseId]` in the path. DAM assets are
234
+ globally scoped and content-addressed via filename hashes.
235
+
142
236
  ## Anti-Patterns
143
237
 
144
238
  - `Record<string, any>` or `Record<string, ComponentType<any>>` — hides type errors
@@ -61,15 +61,25 @@ export default [heroContract, buttonContract];
61
61
 
62
62
  ## Layout Contract API
63
63
 
64
+ Use `defineLayoutContract`, `slot()`, and `fragmentDefault()` from the SDK:
65
+
64
66
  ```ts
67
+ // src/cms/contracts/layouts/index.ts
68
+ import { defineLayoutContract, slot, fragmentDefault } from '@gradial/aci';
69
+
65
70
  export default [
66
- {
71
+ defineLayoutContract({
67
72
  name: 'marketing',
68
73
  slots: [
69
- { name: 'main', required: true },
70
- { name: 'footer', required: false },
74
+ slot('header'),
75
+ slot('main', true),
76
+ slot('footer'),
71
77
  ],
72
- },
78
+ defaults: {
79
+ header: fragmentDefault('site-header'),
80
+ footer: fragmentDefault('site-footer'),
81
+ },
82
+ }),
73
83
  ];
74
84
  ```
75
85
 
@@ -79,12 +89,14 @@ Content pages reference layouts by name:
79
89
  {
80
90
  "layout": "marketing",
81
91
  "regions": {
82
- "main": [...],
83
- "footer": [...]
92
+ "main": [...]
84
93
  }
85
94
  }
86
95
  ```
87
96
 
97
+ The `header` and `footer` slots are filled automatically by fragment defaults
98
+ when the page doesn't provide blocks for them.
99
+
88
100
  ## Multiple Runtime Mappings
89
101
 
90
102
  One runtime component may implement multiple contract names. Define
@@ -117,7 +129,7 @@ capabilities:
117
129
  fragmentRender: true
118
130
 
119
131
  routes:
120
- cmsManaged: "/[...slug]"
132
+ cmsManaged: "/[[...slug]]"
121
133
  frameworkOwned:
122
134
  - "/api/*"
123
135
  - "/_next/*" # Next.js
@@ -151,3 +163,7 @@ rendererProtocol: stdio-json
151
163
 
152
164
  - `componentRegistry` and `layoutRegistry` point to contract-only modules.
153
165
  - No secrets in `.aci.yaml`, docs, fixtures, or generated files.
166
+ - The `cmsManaged` route pattern should match the filesystem: use `/[[...slug]]`
167
+ (optional catch-all) for Next.js repos. Production middleware always rewrites
168
+ to `/__r/{releaseId}/routes/{path}` so slug is non-empty, but file-provider
169
+ local dev passes requests unrewritten so `/` arrives with an empty slug.
@@ -1,7 +1,21 @@
1
1
  # Repository Structure Reference
2
2
 
3
+ ## Generation Modes
4
+
5
+ The `aci init` command generates sites in two modes:
6
+
7
+ | Mode | Command | What you get |
8
+ |------|---------|-------------|
9
+ | **Scaffold** (default) | `aci init my-site --template next-starter` | Golden-path skeleton: config, framework wiring, page route, empty registry, design system, agent skills. No demo content or components. This is the starting point for a new customer. |
10
+ | **With starter content** | `aci init my-site --template next-starter --with-starter-content` | Scaffold + demo content (`.content/`), demo components (sections, elements, chrome), full registry, stories, and fixtures. Use for demos or learning. |
11
+
12
+ Available templates: `next-starter`, `astro-starter`.
13
+
3
14
  ## Directory Layout
4
15
 
16
+ The scaffold generates the structure below. Items marked **(starter content only)** are
17
+ only present when `--with-starter-content` is used.
18
+
5
19
  ```
6
20
  .aci.yaml ACI config: contract paths, framework, routes
7
21
  package.json Dependencies and scripts
@@ -11,7 +25,7 @@ tsconfig.json TypeScript config
11
25
  site.json Site metadata, navigation, footer, branding, SEO
12
26
  pages/
13
27
  home/_index.json Homepage (route /)
14
- about/_index.json About page (route /about)
28
+ about/_index.json About page (route /about) (starter content only)
15
29
  fragments/
16
30
  navbar/_index.json Navbar fragment (layout default)
17
31
  site-footer/_index.json Footer fragment (layout default)
@@ -19,14 +33,14 @@ src/
19
33
  cms/
20
34
  contracts/
21
35
  components/ Component contracts (framework-agnostic)
22
- homeHero.contract.ts Hero section contract
23
- button.contract.ts Button block contract
24
- container.contract.ts Container (nested blocks) contract
25
- featureGrid.contract.ts Feature grid contract
26
- featureCard.contract.ts Feature card contract
27
- statsBar.contract.ts Stats bar contract
28
- siteNavigation.contract.ts Site navigation contract
29
- siteFooter.contract.ts Site footer contract
36
+ homeHero.contract.ts Hero section contract (starter content only)
37
+ button.contract.ts Button block contract (starter content only)
38
+ container.contract.ts Container (nested blocks) contract (starter content only)
39
+ featureGrid.contract.ts Feature grid contract (starter content only)
40
+ featureCard.contract.ts Feature card contract (starter content only)
41
+ statsBar.contract.ts Stats bar contract (starter content only)
42
+ siteNavigation.contract.ts Site navigation contract (starter content only)
43
+ siteFooter.contract.ts Site footer contract (starter content only)
30
44
  index.ts Component contract barrel
31
45
  layouts/
32
46
  index.ts Layout contract barrel (plain objects)
@@ -55,7 +69,7 @@ src/
55
69
  index.css Barrel import for all token files
56
70
  app/ Framework routes (Next.js App Router)
57
71
  layout.tsx Root layout (minimal, imports CSS only)
58
- [[...slug]]/page.tsx Optional catch-all for all CMS pages
72
+ [[...slug]]/page.tsx Optional catch-all for all CMS pages
59
73
  pages/ Framework routes (Astro file-based routing)
60
74
  [...slug].astro Catch-all for all CMS pages
61
75
  layouts/ Page layout wrappers (framework-specific)
@@ -87,11 +101,11 @@ Only Next.js and Astro are supported. SvelteKit support has been removed.
87
101
  | Concern | Path |
88
102
  |---------|------|
89
103
  | Routes | `src/app/[[...slug]]/page.tsx` (optional catch-all, `force-dynamic`) |
90
- | Root layout | `src/app/layout.tsx` (minimal — imports CSS, no chrome) |
91
- | Config | `next.config.ts` with `withAci()` |
104
+ | Root layout | `src/app/layout.tsx` (minimal — imports CSS, includes `<DevRefresh />`, no chrome) |
105
+ | Config | `next.config.ts` with bare `withAci()` |
92
106
  | Registry | `src/cms/registry.ts` (shared, `createRegistry` with `[contract, component]` tuples) |
93
- | Middleware | `src/middleware.ts` |
94
- | Asset routes | `src/app/gradial/assets/[releaseId]/[...path]/route.ts` |
107
+ | Middleware | `src/middleware.ts` with `createGradialMiddleware({ siteId })` and SDK-exported matcher |
108
+ | Asset route | `src/app/gradial/assets/[...path]/route.ts` — thin SDK re-export from `@gradial/aci/next/asset-route`. Serves CMS documents (robots.txt, sitemap.xml) via withAci rewrites and local-dev DAM assets. Hand-rolled asset routes and the legacy release-scoped `[releaseId]/[...path]` variant are banned. Astro has no route (CDN/global path). |
95
109
 
96
110
  ### Astro
97
111
 
@@ -145,13 +159,15 @@ Standard scripts across all frameworks:
145
159
  "dev": "...",
146
160
  "build": "...",
147
161
  "typecheck": "...",
148
- "aci:compile": "aci build --compile-only",
149
- "content:compile": "aci build --skip-code --content ./.content --out ./.aci/compiled",
150
- "aci:build": "aci build --content ./.content",
151
- "aci:doctor": "aci doctor",
152
- "aci:validate": "aci build --validate-only"
162
+ "test": "node --import tsx --test tests/**/*.test.ts",
163
+ "content:compile": "aci content compile --content .content --out .aci/compiled"
153
164
  }
154
165
  }
155
166
  ```
156
167
 
168
+ The `dev` command auto-compiles content; no manual compile step is needed
169
+ before starting the dev server.
170
+
157
171
  Customer repos use `npm` as the package manager. The monorepo uses `pnpm`.
172
+
173
+ **Note**: `aci doctor` has been removed. Use the `@gradial/aci/testing` conformance preset in CI instead.