@gradial/aci 0.1.31 → 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 (159) hide show
  1. package/README.md +4 -4
  2. package/bin/aci-darwin-arm64 +0 -0
  3. package/bin/aci.js +29 -31
  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/dev/content-watch.d.ts +1 -0
  11. package/dist/dev/content-watch.js +1 -1
  12. package/dist/next/config.d.ts +6 -0
  13. package/dist/next/config.js +20 -13
  14. package/dist/next/content-watch.d.ts +1 -0
  15. package/dist/next/content-watch.js +2 -2
  16. package/dist/next/index.d.ts +1 -0
  17. package/dist/next/index.js +1 -0
  18. package/dist/next/middleware.d.ts +3 -2
  19. package/dist/next/middleware.js +40 -7
  20. package/dist/next/page.d.ts +13 -5
  21. package/dist/next/page.js +42 -17
  22. package/dist/next/server.d.ts +1 -1
  23. package/dist/next/server.js +7 -8
  24. package/dist/testing/conformance/checks.d.ts +12 -0
  25. package/dist/testing/conformance/checks.js +483 -0
  26. package/dist/testing/conformance/index.d.ts +3 -0
  27. package/dist/testing/conformance/index.js +2 -0
  28. package/dist/testing/conformance/preset.d.ts +14 -0
  29. package/dist/testing/conformance/preset.js +98 -0
  30. package/dist/testing/conformance/types.d.ts +45 -0
  31. package/dist/testing/conformance/types.js +7 -0
  32. package/package.json +17 -9
  33. package/src/cli/materialize-template.mjs +133 -0
  34. package/src/cli/validate-content.mjs +0 -0
  35. package/src/cli/verify-next-cdn-routes.mjs +10 -19
  36. package/templates/astro/starter-content/src/cms/contracts/components/button.contract.ts +19 -0
  37. package/templates/astro/starter-content/src/cms/contracts/components/container.contract.ts +19 -0
  38. package/templates/astro/starter-content/src/cms/contracts/components/featureCard.contract.ts +15 -0
  39. package/templates/astro/starter-content/src/cms/contracts/components/featureGrid.contract.ts +16 -0
  40. package/templates/astro/starter-content/src/cms/contracts/components/homeHero.contract.ts +36 -0
  41. package/templates/astro/starter-content/src/cms/contracts/components/index.ts +21 -0
  42. package/templates/astro/starter-content/src/cms/contracts/components/statsBar.contract.ts +19 -0
  43. package/templates/astro/starter-content/src/cms/registry.ts +28 -0
  44. package/templates/astro/template/{.aci.yaml → .aci.yaml.tmpl} +1 -1
  45. package/templates/astro/template/README.md.tmpl +53 -0
  46. package/templates/astro/template/gitignore +12 -0
  47. package/templates/astro/template/package.json.tmpl +5 -3
  48. package/templates/astro/template/src/cms/contracts/components/index.ts +3 -0
  49. package/templates/astro/template/src/cms/registry.ts +3 -26
  50. package/templates/astro/template/src/pages/[...slug].astro +13 -35
  51. package/templates/astro/template/tests/conformance.test.ts.tmpl +12 -0
  52. package/templates/common/.agents/AGENTS.md +3 -5
  53. package/templates/common/.agents/skills/aci/SKILL.md +72 -23
  54. package/templates/common/.agents/skills/authoring/SKILL.md +53 -14
  55. package/templates/common/.agents/skills/authoring/references/block-registry.md +109 -15
  56. package/templates/common/.agents/skills/authoring/references/contracts.md +23 -7
  57. package/templates/common/.agents/skills/authoring/references/repo-structure.md +35 -19
  58. package/templates/common/.agents/skills/authoring/references/sdk-server-functions.md +75 -17
  59. package/templates/common/.agents/skills/migration/SKILL.md +7 -1
  60. package/templates/common/.agents/skills/migration/references/canon-migration.md +471 -0
  61. package/templates/common/.content/config/site.json.tmpl +15 -0
  62. package/templates/common/.content/pages/home/_index.json +4 -103
  63. package/templates/common/.nvmrc +1 -0
  64. package/templates/common/README.md.tmpl +32 -11
  65. package/templates/common/contracts/components/index.ts +1 -19
  66. package/templates/common/design-system/tokens/colors.css +34 -10
  67. package/templates/common/starter-content/.content/config/robots.txt +2 -0
  68. package/templates/common/starter-content/.content/pages/home/_index.json +112 -0
  69. package/templates/common/starter-content/.content/schemas/site.schema.json +138 -0
  70. package/templates/nextjs/starter-content/src/cms/contracts/components/container.contract.ts +19 -0
  71. package/templates/nextjs/starter-content/src/cms/contracts/components/featureCard.contract.ts +15 -0
  72. package/templates/nextjs/starter-content/src/cms/contracts/components/featureGrid.contract.ts +16 -0
  73. package/templates/nextjs/starter-content/src/cms/contracts/components/index.ts +21 -0
  74. package/templates/nextjs/starter-content/src/cms/contracts/components/siteFooter.contract.ts +45 -0
  75. package/templates/nextjs/starter-content/src/cms/contracts/components/siteNavigation.contract.ts +30 -0
  76. package/templates/nextjs/starter-content/src/cms/contracts/components/statsBar.contract.ts +19 -0
  77. package/templates/nextjs/starter-content/src/cms/registry.ts +28 -0
  78. package/templates/nextjs/template/{.aci.yaml → .aci.yaml.tmpl} +1 -1
  79. package/templates/nextjs/template/README.md.tmpl +52 -0
  80. package/templates/nextjs/template/gitignore +15 -0
  81. package/templates/nextjs/template/next.config.mjs +1 -6
  82. package/templates/nextjs/template/package.json.tmpl +5 -3
  83. package/templates/nextjs/template/src/app/gradial/assets/[...path]/route.ts +1 -4
  84. package/templates/nextjs/template/src/cms/contracts/components/index.ts +1 -13
  85. package/templates/nextjs/template/src/cms/registry.ts +3 -26
  86. package/templates/nextjs/template/src/design-system/styles.css +346 -0
  87. package/templates/nextjs/template/src/design-system/tokens/colors.css +110 -0
  88. package/templates/nextjs/template/src/design-system/tokens/components.css +86 -0
  89. package/templates/nextjs/template/src/design-system/tokens/index.css +6 -0
  90. package/templates/nextjs/template/src/design-system/tokens/radii.css +5 -0
  91. package/templates/nextjs/template/src/design-system/tokens/shadows.css +9 -0
  92. package/templates/nextjs/template/src/design-system/tokens/spacing.css +26 -0
  93. package/templates/nextjs/template/src/design-system/tokens/typography.css +32 -0
  94. package/templates/nextjs/template/src/middleware.ts.tmpl +12 -0
  95. package/templates/nextjs/template/tests/conformance.test.ts.tmpl +12 -0
  96. package/templates/nextjs/template/src/cms/contracts/components/container.contract.ts +0 -20
  97. package/templates/nextjs/template/src/middleware.ts +0 -7
  98. /package/templates/{nextjs/template → astro/starter-content}/src/cms/contracts/components/siteFooter.contract.ts +0 -0
  99. /package/templates/{nextjs/template → astro/starter-content}/src/cms/contracts/components/siteNavigation.contract.ts +0 -0
  100. /package/templates/astro/{template → starter-content}/src/components/chrome/Footer.astro +0 -0
  101. /package/templates/astro/{template → starter-content}/src/components/chrome/Header.astro +0 -0
  102. /package/templates/astro/{template → starter-content}/src/components/elements/AtomicBlock.astro +0 -0
  103. /package/templates/astro/{template → starter-content}/src/components/elements/AtomicBlocks.astro +0 -0
  104. /package/templates/astro/{template → starter-content}/src/components/elements/Button.astro +0 -0
  105. /package/templates/astro/{template → starter-content}/src/components/elements/ButtonBlock.astro +0 -0
  106. /package/templates/astro/{template → starter-content}/src/components/elements/Container.astro +0 -0
  107. /package/templates/astro/{template → starter-content}/src/components/elements/Icon.astro +0 -0
  108. /package/templates/astro/{template → starter-content}/src/components/elements/Logo.astro +0 -0
  109. /package/templates/astro/{template → starter-content}/src/components/elements/RemoteImage.astro +0 -0
  110. /package/templates/astro/{template → starter-content}/src/components/sections/ContainerSection.astro +0 -0
  111. /package/templates/astro/{template → starter-content}/src/components/sections/FeatureCard.astro +0 -0
  112. /package/templates/astro/{template → starter-content}/src/components/sections/FeatureGrid.astro +0 -0
  113. /package/templates/astro/{template → starter-content}/src/components/sections/HomeHero.astro +0 -0
  114. /package/templates/astro/{template → starter-content}/src/components/sections/StatsBar.astro +0 -0
  115. /package/templates/astro/{template → starter-content}/src/fixtures/atomicBlocks.ts +0 -0
  116. /package/templates/astro/{template → starter-content}/src/fixtures/composition.ts +0 -0
  117. /package/templates/astro/{template → starter-content}/src/fixtures/index.ts +0 -0
  118. /package/templates/astro/{template → starter-content}/src/fixtures/sections.ts +0 -0
  119. /package/templates/astro/{template → starter-content}/src/render/RenderPage.astro +0 -0
  120. /package/templates/astro/{template → starter-content}/src/stories/CompositionStoryFrame.astro +0 -0
  121. /package/templates/astro/{template → starter-content}/src/stories/HomeHero.stories.ts +0 -0
  122. /package/templates/astro/{template → starter-content}/src/stories/SectionStoryFrame.astro +0 -0
  123. /package/templates/astro/{template → starter-content}/src/stories/StoryDocFrame.astro +0 -0
  124. /package/templates/common/{.content → starter-content/.content}/.gradial-dam/blobs/sha256/7f/bf/7fbfc7fe230ff12688c496f660119e41d9725c4504d82e310c15665c73c09292 +0 -0
  125. /package/templates/common/{.content → starter-content/.content}/.gradial-dam/blobs/sha256/a0/f5/a0f506689de9b15a7055f70103a3f02cd3ba105771ae2de6754df2ede1210d3a +0 -0
  126. /package/templates/common/{.content → starter-content/.content}/.gradial-dam/blobs/sha256/dd/b7/ddb7e9642be08317bcd4b2bd26b58ba8d0d34dd7239e03e684a4f3634fa3845b +0 -0
  127. /package/templates/common/{.content → starter-content/.content}/.gradial-dam/blobs/sha256/fb/32/fb3260e8286ea8231ea1c77b9c0be92d6edd9026565731eb14cfdcfd6b1b75b3 +0 -0
  128. /package/templates/common/{.content → starter-content/.content}/config/site.json +0 -0
  129. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_about_hero/asset.json +0 -0
  130. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_about_hero/editorial.json +0 -0
  131. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_about_hero/governance.json +0 -0
  132. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_about_hero/versions/v0001.json +0 -0
  133. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_home_hero/asset.json +0 -0
  134. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_home_hero/editorial.json +0 -0
  135. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_home_hero/governance.json +0 -0
  136. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_home_hero/versions/v0001.json +0 -0
  137. /package/templates/common/{.content → starter-content/.content}/fragments/navbar/_index.json +0 -0
  138. /package/templates/common/{.content → starter-content/.content}/fragments/site-footer/_index.json +0 -0
  139. /package/templates/common/{.content → starter-content/.content}/pages/about/_index.json +0 -0
  140. /package/templates/common/{public → starter-content/public}/assets/Dwire-model.png +0 -0
  141. /package/templates/common/{public → starter-content/public}/assets/seattle.avif +0 -0
  142. /package/templates/nextjs/{template → starter-content}/src/cms/contracts/components/button.contract.ts +0 -0
  143. /package/templates/nextjs/{template → starter-content}/src/cms/contracts/components/homeHero.contract.ts +0 -0
  144. /package/templates/nextjs/{template → starter-content}/src/components/chrome/Footer.tsx +0 -0
  145. /package/templates/nextjs/{template → starter-content}/src/components/chrome/Header.tsx +0 -0
  146. /package/templates/nextjs/{template → starter-content}/src/components/elements/AtomicBlock.tsx +0 -0
  147. /package/templates/nextjs/{template → starter-content}/src/components/elements/Button.tsx +0 -0
  148. /package/templates/nextjs/{template → starter-content}/src/components/elements/ButtonBlock.tsx +0 -0
  149. /package/templates/nextjs/{template → starter-content}/src/components/elements/Container.tsx +0 -0
  150. /package/templates/nextjs/{template → starter-content}/src/components/elements/Icon.tsx +0 -0
  151. /package/templates/nextjs/{template → starter-content}/src/components/elements/Logo.tsx +0 -0
  152. /package/templates/nextjs/{template → starter-content}/src/components/sections/ContainerSection.tsx +0 -0
  153. /package/templates/nextjs/{template → starter-content}/src/components/sections/FeatureCard.tsx +0 -0
  154. /package/templates/nextjs/{template → starter-content}/src/components/sections/FeatureGrid.tsx +0 -0
  155. /package/templates/nextjs/{template → starter-content}/src/components/sections/HomeHero.tsx +0 -0
  156. /package/templates/nextjs/{template → starter-content}/src/components/sections/StatsBar.tsx +0 -0
  157. /package/templates/nextjs/{template → starter-content}/src/stories/Button.stories.tsx +0 -0
  158. /package/templates/nextjs/{template → starter-content}/src/stories/Header.stories.tsx +0 -0
  159. /package/templates/nextjs/{template → starter-content}/src/stories/HomeHero.stories.tsx +0 -0
@@ -7,13 +7,19 @@ across preview, local file, and deployed S3 modes. **Always use these
7
7
  functions instead of directly importing `FileContentProvider` or
8
8
  `S3ContentProvider`.** The SDK handles the resolution chain automatically.
9
9
 
10
- ### `createPage(registry)`
10
+ ### `createPage(registry, options?)`
11
11
 
12
12
  The catch-all route handler for Next.js App Router. Owns param
13
13
  normalization, content loading via render input, metadata generation,
14
14
  `notFound` handling, layout resolution, and block rendering via the
15
15
  registry.
16
16
 
17
+ Path shape: in production, middleware rewrites to `/__r/{releaseId}/routes/{...slug}`.
18
+ The SDK's `resolveRouteAndRelease` decomposes `['__r', releaseId, 'routes', ...pathSegments]`.
19
+ In local dev (`ACI_CONTENT_PROVIDER=file`), requests pass through unrewritten, so `/`
20
+ arrives with an empty/undefined slug. The filesystem uses an **optional catch-all**
21
+ `[[...slug]]` to handle both shapes.
22
+
17
23
  ```tsx
18
24
  // src/app/[[...slug]]/page.tsx
19
25
  import { createPage } from '@gradial/aci/next';
@@ -30,20 +36,32 @@ page regions or layout fragment defaults), and renders blocks via the
30
36
  registry. The `main` slot is wrapped in `<main>`; other slots render as
31
37
  fragments.
32
38
 
33
- ### `withAci(nextConfig, options?)`
39
+ ### `withAci(nextConfig?, options?)`
34
40
 
35
- Next.js config wrapper. Sets up asset rewrites (`.gradial-dam` prefix)
36
- and dev-mode content watch via webpack plugin.
41
+ Next.js config wrapper. Owns all canonical SDK behavior: output mode,
42
+ outputFileTracingRoot, base-path/build-mode/dist-dir env injection, CDN
43
+ headers, asset rewrites (`/.gradial-dam` prefix), content-watch plugin,
44
+ and public-path scanning. Repos should pass a bare `withAci()` call and
45
+ rely on SDK defaults.
37
46
 
38
47
  ```ts
39
48
  // next.config.ts
40
- import { withAci } from '@gradial/aci/next';
41
- import type { NextConfig } from 'next';
49
+ import { withAci } from '@gradial/aci/next/config';
50
+
51
+ export default withAci();
52
+ ```
42
53
 
43
- const nextConfig: NextConfig = { reactStrictMode: true };
44
- export default withAci(nextConfig);
54
+ For site-specific non-CMS routes, pass `passthroughRoutes`:
55
+
56
+ ```ts
57
+ export default withAci({
58
+ passthroughRoutes: ['/api/*', '/admin/*'],
59
+ });
45
60
  ```
46
61
 
62
+ Do not manually configure outputFileTracing, base-path, build-mode, dist-dir,
63
+ or custom rewrites/redirects for ACI concerns. The SDK handles these.
64
+
47
65
  ### `getSiteConfig<T>()`
48
66
 
49
67
  Loads the site configuration. Checks the render input header first
@@ -95,6 +113,38 @@ const previewRelease = await isPreviewMode();
95
113
  if (previewRelease) { /* preview-specific logic */ }
96
114
  ```
97
115
 
116
+ ### `createGradialMiddleware(config)`
117
+
118
+ Creates Next.js middleware for ACI sites. Requires explicit `siteId`.
119
+
120
+ ```ts
121
+ // src/middleware.ts
122
+ import { createGradialMiddleware } from '@gradial/aci/next/middleware';
123
+
124
+ export default createGradialMiddleware({ siteId: 'your-tenant-id' });
125
+ export { config } from '@gradial/aci/next/middleware';
126
+ ```
127
+
128
+ The SDK exports a canonical middleware matcher via the `config` export. Repos
129
+ should re-export it verbatim. Do not hand-roll custom matcher patterns unless
130
+ site-specific static paths require exclusions.
131
+
132
+ **Config interface:**
133
+
134
+ ```ts
135
+ export interface GradialMiddlewareConfig {
136
+ siteId: string; // REQUIRED
137
+ edgeConfig?: string;
138
+ previewSignKey?: string;
139
+ deploymentId?: string;
140
+ deploymentProtectionBypass?: string;
141
+ }
142
+ ```
143
+
144
+ The middleware rewrites public URLs to `/__r/{releaseId}/routes/{path}` for
145
+ the app-level route. The SDK extracts releaseId and resolves the content route
146
+ internally.
147
+
98
148
  ### `DevRefresh`
99
149
 
100
150
  React component for dev-mode content refresh. Connects to the SDK's
@@ -108,8 +158,8 @@ import { DevRefresh } from '@gradial/aci/next';
108
158
  <DevRefresh />
109
159
  ```
110
160
 
111
- Only active in development (`NODE_ENV !== 'production'`). Renders `null`
112
- in production.
161
+ **Mandatory** for Next.js repos. Only active in development
162
+ (`NODE_ENV !== 'production'`). Renders `null` in production.
113
163
 
114
164
  ### Error Classes
115
165
 
@@ -158,15 +208,25 @@ app code.** Always use `getPage()`, `getSiteConfig()`, `getFragment()`,
158
208
  `getLayout()`, or `getRenderInput()` from `@gradial/aci/next`. The SDK
159
209
  handles the resolution chain automatically.
160
210
 
161
- The only exception is the asset route handler
162
- (`src/app/gradial/assets/[releaseId]/[...path]/route.ts`), which directly
211
+ The only exception is the transitional asset route handler
212
+ (`src/app/gradial/assets/[...path]/route.ts`), which directly
163
213
  uses `FileContentProvider` for local asset serving and
164
- `getReleaseAssetResponse` for S3 assets.
214
+ `getReleaseAssetResponse` for S3 assets. The preferred path is to use no
215
+ app-level asset route at all (`withAci()` handles rewrites for Next.js;
216
+ CloudFront function rewrite for Astro). DAM derivative assets are
217
+ content-addressed and globally shared — staged once at `{keyPrefix}/.gradial-dam/`
218
+ and shared across all releases.
165
219
 
166
220
  ## Layout.tsx Pattern
167
221
 
222
+ The root layout is minimal: import CSS, optionally load site config, and
223
+ include `<DevRefresh />`. Chrome (Header/Footer) is **not** in layout.tsx —
224
+ it is CMS-managed via layout fragment defaults and rendered by `createPage`.
225
+
168
226
  ```tsx
169
227
  import { DevRefresh, getSiteConfig } from '@gradial/aci/next';
228
+ import type { ReactNode } from 'react';
229
+ import '@/design-system/styles.css';
170
230
 
171
231
  export default async function Layout({ children }: { children: ReactNode }) {
172
232
  const siteConfig = await getSiteConfig<SiteConfig>().catch(() => null);
@@ -181,10 +241,8 @@ export default async function Layout({ children }: { children: ReactNode }) {
181
241
  }
182
242
  ```
183
243
 
184
- Chrome (Header/Footer) is CMS-managed via layout fragment defaults and
185
- rendered by `createPage` through the registry. The `layout.tsx` file
186
- should be minimal — import CSS, optionally load site config for
187
- metadata/fonts, and render `<DevRefresh />`.
244
+ `<DevRefresh />` is **mandatory** for Next.js repos. It provides dev-mode
245
+ content hot-reload.
188
246
 
189
247
  For sites that need site config in the layout (e.g., for fonts or
190
248
  metadata), use `getSiteConfig<SiteConfig>()` with a `.catch(() => null)`
@@ -134,11 +134,17 @@ discipline rules, and interactivity approach.
134
134
  Read `references/selector-map.md` for the source-to-target selector
135
135
  mapping format that tracks every migrated element.
136
136
 
137
+ ## Deep Dive: Canon Migration
138
+
139
+ Read `references/canon-migration.md` for per-repo migration guidance to the
140
+ frontend canon. This document provides specific migration steps for each repo
141
+ based on the survey divergence matrix (honeywell, optimum, bnc, website-v2).
142
+
137
143
  ## Validation Gates
138
144
 
139
145
  Before considering any change complete:
140
146
 
141
- 1. Run `npm run aci:validate` — content validation
147
+ 1. Run `npm run aci:compile` — contract + content compile (validates content)
142
148
  2. Run `npm run typecheck && npm run build` — CI build steps
143
149
  3. Run visual regression comparisons
144
150
  4. Verify Storybook stories render correctly
@@ -0,0 +1,471 @@
1
+ # Canon Migration Reference
2
+
3
+ This document provides repo-specific migration guidance to the frontend canon.
4
+ Use it alongside the canonical spec (`tasks/frontend-canon-decisions.md`) and
5
+ survey divergence matrix (`tasks/frontend-canon-survey.md`).
6
+
7
+ ---
8
+
9
+ ## General Migration Steps (All Repos)
10
+
11
+ ### 1. Update SDK Version
12
+
13
+ All repos to `@gradial/aci` version `^0.1.31` (caret range, no exact pinning).
14
+
15
+ ```bash
16
+ npm install @gradial/aci@^0.1.31
17
+ ```
18
+
19
+ ### 2. Update Zod Version
20
+
21
+ All repos to `zod` version `^4.0.0` (latest major, caret range).
22
+
23
+ ```bash
24
+ npm install zod@^4.0.0
25
+ ```
26
+
27
+ ### 3. Bare `withAci()` Config
28
+
29
+ Simplify framework config to bare `withAci()` call:
30
+
31
+ **Next.js:**
32
+ ```ts
33
+ // next.config.ts
34
+ import { withAci } from '@gradial/aci/next/config';
35
+ export default withAci();
36
+ ```
37
+
38
+ **Astro:**
39
+ ```mjs
40
+ // astro.config.mjs
41
+ import { withAci } from '@gradial/aci/astro';
42
+ export default defineConfig({
43
+ integrations: [withAci()],
44
+ });
45
+ ```
46
+
47
+ Delete manual env-var plumbing, outputFileTracing, custom rewrites/redirects
48
+ for ACI concerns.
49
+
50
+ ### 4. Middleware: Require `siteId`
51
+
52
+ Update middleware to pass explicit `siteId`:
53
+
54
+ ```ts
55
+ // src/middleware.ts
56
+ import { createGradialMiddleware } from '@gradial/aci/next/middleware';
57
+ export { config } from '@gradial/aci/next/middleware';
58
+ export default createGradialMiddleware({ siteId: 'your-tenant-id' });
59
+ ```
60
+
61
+ Re-export the SDK's canonical matcher via `config`. Delete custom matchers
62
+ unless site-specific static paths require exclusions.
63
+
64
+ ### 5. Path Shape: `[[...slug]]` (Optional Catch-All)
65
+
66
+ Ensure the filesystem route is `[[...slug]]` (optional catch-all):
67
+
68
+ ```bash
69
+ # If the repo has a required catch-all, rename to optional:
70
+ mv src/app/\[...slug\] src/app/\[\[...slug\]\]
71
+ ```
72
+
73
+ Update `.aci.yaml`:
74
+ ```yaml
75
+ routes:
76
+ cmsManaged: "/[[...slug]]"
77
+ ```
78
+
79
+ Production middleware always rewrites to `/__r/{releaseId}/routes/{path}` so
80
+ slug is non-empty there, but file-provider local dev passes requests
81
+ unrewritten so `/` arrives with an empty slug. A required catch-all 404s
82
+ the dev homepage.
83
+
84
+ ### 6. DevRefresh Mandated
85
+
86
+ Add `<DevRefresh />` to the root layout:
87
+
88
+ ```tsx
89
+ // src/app/layout.tsx
90
+ import { DevRefresh } from '@gradial/aci/next';
91
+
92
+ export default function Layout({ children }: { children: ReactNode }) {
93
+ return (
94
+ <html lang="en">
95
+ <body>
96
+ {children}
97
+ <DevRefresh />
98
+ </body>
99
+ </html>
100
+ );
101
+ }
102
+ ```
103
+
104
+ ### 7. Layout Contracts: Use SDK Helpers
105
+
106
+ Replace plain object literals with SDK helpers:
107
+
108
+ ```ts
109
+ // src/cms/contracts/layouts/index.ts
110
+ import { defineLayoutContract, slot, fragmentDefault } from '@gradial/aci';
111
+
112
+ export default [
113
+ defineLayoutContract({
114
+ name: 'default',
115
+ slots: [
116
+ slot('header'),
117
+ slot('main', true),
118
+ slot('footer'),
119
+ ],
120
+ defaults: {
121
+ header: fragmentDefault('navbar'),
122
+ footer: fragmentDefault('site-footer'),
123
+ },
124
+ }),
125
+ ];
126
+ ```
127
+
128
+ ### 8. Content Compilation: Standardize CLI Command
129
+
130
+ Update `package.json`:
131
+
132
+ ```json
133
+ {
134
+ "scripts": {
135
+ "content:compile": "aci content compile --content .content --out .aci/compiled"
136
+ }
137
+ }
138
+ ```
139
+
140
+ Replace bespoke scripts like `gradial-aci build --skip-code ...` with the
141
+ standard command.
142
+
143
+ ### 9. Storybook: Upgrade to v10
144
+
145
+ For repos on Storybook 9, upgrade to Storybook 10:
146
+
147
+ **Next.js:**
148
+ ```bash
149
+ npx storybook@latest upgrade
150
+ npm install --save-dev @storybook/nextjs-vite
151
+ ```
152
+
153
+ **Astro:**
154
+ ```bash
155
+ npx storybook@latest upgrade
156
+ npm install --save-dev @storybook-astro/framework
157
+ ```
158
+
159
+ ### 10. Replace `aci doctor` with Testing Preset
160
+
161
+ Delete references to `npm run aci:doctor` and `aci doctor` commands. Add the
162
+ SDK testing preset to your CI test suite:
163
+
164
+ ```ts
165
+ // tests/conformance.test.ts
166
+ import { conformancePreset } from '@gradial/aci/testing';
167
+
168
+ conformancePreset({
169
+ framework: 'next',
170
+ siteId: 'your-tenant-id',
171
+ });
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Repo-Specific Migration Notes
177
+
178
+ ### Honeywell Aerospace (aci-honeywell-aerospace)
179
+
180
+ **Most divergent Next.js repo.** Requires the most comprehensive migration.
181
+
182
+ #### Critical Changes
183
+
184
+ 1. **Replace Custom Page Entry with `createPage`**
185
+
186
+ Delete `src/app/[[...slug]]/page.tsx` (60+ line custom implementation) and
187
+ replace with canonical SDK pattern:
188
+
189
+ ```tsx
190
+ // src/app/[[...slug]]/page.tsx
191
+ import { createPage } from '@gradial/aci/next/page';
192
+ import { registry } from '@/cms/registry';
193
+
194
+ const page = createPage(registry);
195
+ export const dynamic = 'force-dynamic';
196
+ export const generateMetadata = page.generateMetadata;
197
+ export default page.default;
198
+ ```
199
+
200
+ This eliminates:
201
+ - Custom `resolveRouteFromParams`
202
+ - Custom `loadPage`/`loadSiteConfig` via `src/lib/content.ts`
203
+ - Custom `<CmsPage>` render component
204
+ - `generateStaticParams` with direct `FileContentProvider` imports
205
+
206
+ 2. **Adopt `createRegistry()` (Delete Hand-Rolled Map)**
207
+
208
+ Replace the hand-rolled `Record<string, ComponentType<any>>` map in
209
+ `src/components/cms/renderBlock.tsx` with a typed registry:
210
+
211
+ ```ts
212
+ // src/cms/registry.ts
213
+ import { createRegistry } from '@gradial/aci';
214
+ import { heroContract } from './contracts/components/hero.contract';
215
+ import { Hero } from '@/components/sections/Hero';
216
+ // ... import all contracts and components
217
+
218
+ export const registry = createRegistry([
219
+ [heroContract, Hero],
220
+ // ... all other [contract, component] tuples
221
+ ]);
222
+ ```
223
+
224
+ The contracts already exist in `src/cms/contracts/` — wire them into the
225
+ registry and delete the hand-rolled map.
226
+
227
+ 3. **Delete Custom Content Provider**
228
+
229
+ Delete `src/lib/content.ts` (thin wrapper over SDK server functions). Use
230
+ SDK server functions directly:
231
+
232
+ ```ts
233
+ import { getSiteConfig, getPage } from '@gradial/aci/next';
234
+ ```
235
+
236
+ 4. **Remove Custom Asset Route**
237
+
238
+ Delete `src/app/gradial/assets/[releaseId]/[...path]/route.ts` (with extra
239
+ `[releaseId]` segment). Replace with standard path:
240
+
241
+ ```ts
242
+ // src/app/gradial/assets/[...path]/route.ts
243
+ export { GET, runtime, dynamic } from '@gradial/aci/next/asset-route';
244
+ ```
245
+
246
+ The SDK asset-route handler resolves releaseId internally. No need for an
247
+ explicit path segment.
248
+
249
+ 5. **Delete `content-assets` Route and `.gradial-dam` Rewrite**
250
+
251
+ Delete `src/app/content-assets/[...key]/route.ts` and remove the
252
+ `.gradial-dam` rewrite from `next.config.ts`. The SDK asset route handles
253
+ both dev-mode file serving and deployed S3 serving.
254
+
255
+ 6. **Delete Runtime `layouts.ts`**
256
+
257
+ Delete `src/cms/layouts.ts` (runtime layout definition duplicating the
258
+ contract-side definition). `createPage` reads layouts from the contract/
259
+ compiled data, not a runtime definition.
260
+
261
+ 7. **Update Layout Contract Defaults**
262
+
263
+ Replace raw `{kind: 'fragment-ref' as const, ...}` objects with
264
+ `fragmentDefault()`:
265
+
266
+ ```ts
267
+ defaults: {
268
+ header: fragmentDefault('navbar'),
269
+ footer: fragmentDefault('site-footer'),
270
+ }
271
+ ```
272
+
273
+ 8. **Update `.aci.yaml` siteId**
274
+
275
+ Change `siteId: site_nextjs` (generic/wrong) to `siteId: honeywell-aerospace`
276
+ (matches tenant name in infrastructure).
277
+
278
+ #### Risk Mitigation
279
+
280
+ - Verify `createPage` handles the honeywell component set without regressions.
281
+ - Test the full page render pipeline after migrating from custom page entry.
282
+ - Ensure all contracts are correctly wired into the new typed registry.
283
+
284
+ ---
285
+
286
+ ### BNC (aci-bnc)
287
+
288
+ **Cleanest follower.** Minimal divergence.
289
+
290
+ #### Required Changes
291
+
292
+ 1. **Update Layout Contract Style**
293
+
294
+ Replace plain object literals with SDK helpers:
295
+
296
+ ```ts
297
+ // src/cms/contracts/layouts/index.ts
298
+ import { defineLayoutContract, slot, fragmentDefault } from '@gradial/aci';
299
+
300
+ export default [
301
+ defineLayoutContract({
302
+ name: 'default',
303
+ slots: [
304
+ slot('header'),
305
+ slot('main', true),
306
+ slot('footer'),
307
+ ],
308
+ defaults: {
309
+ header: fragmentDefault('navbar'),
310
+ footer: fragmentDefault('site-footer'),
311
+ },
312
+ }),
313
+ ];
314
+ ```
315
+
316
+ 2. **Add `siteId` to Middleware**
317
+
318
+ Update middleware to pass explicit `siteId`:
319
+
320
+ ```ts
321
+ export default createGradialMiddleware({ siteId: 'bnc' });
322
+ ```
323
+
324
+ 3. **General Migration Steps**
325
+
326
+ Apply all general migration steps (SDK version, Zod version, Storybook
327
+ upgrade, etc.).
328
+
329
+ ---
330
+
331
+ ### Optimum (aci-optimum)
332
+
333
+ **Only Astro repo in production.** Needs Astro-specific patterns.
334
+
335
+ #### Required Changes
336
+
337
+ 1. **Replace Manual Page Entry with `getPageData()`**
338
+
339
+ Replace the ~60-line manual page entry in `src/pages/[...slug].astro` with
340
+ the SDK's `getPageData()` helper (the Astro equivalent of Next.js `createPage`):
341
+
342
+ ```astro
343
+ ---
344
+ // src/pages/[...slug].astro
345
+ import { getGradialStaticPaths, getPageData, createRenderChildren } from '@gradial/aci/astro';
346
+ import { registry } from '../cms/registry';
347
+ import SiteLayout from '../layouts/SiteLayout.astro';
348
+
349
+ export const getStaticPaths = getGradialStaticPaths;
350
+
351
+ const data = await getPageData(Astro);
352
+ const renderChildren = createRenderChildren(registry);
353
+ ---
354
+ <!-- render data.layout.slots via renderChildren -->
355
+ ```
356
+
357
+ This eliminates all direct `FileContentProvider` imports and manual
358
+ layout/slot resolution from the page entry.
359
+
360
+ For DAM asset URLs, use `damAssetUrl()` from `@gradial/aci/astro`:
361
+
362
+ ```astro
363
+ ---
364
+ import { damAssetUrl } from '@gradial/aci/astro';
365
+ ---
366
+ <img src={damAssetUrl('images/hero.webp')} alt="Hero" />
367
+ ```
368
+
369
+ 2. **CloudFront Asset Behavior (Infrastructure Change)**
370
+
371
+ DAM derivative assets are content-addressed and globally shared — staged once
372
+ at the hosting bucket's global `.gradial-dam/` location and shared across all
373
+ releases. The existing CloudFront function in
374
+ `infra/modules/hosting-cloudfront-site/function.js.tftpl` already handles the
375
+ rewrite automatically, rewriting `/.gradial-dam/*` requests to
376
+ `{keyPrefix}/.gradial-dam/*` with no release lookup.
377
+
378
+ **No infrastructure changes are needed** — the CloudFront function rewrite is
379
+ already in place and handles DAM asset serving automatically with immutable
380
+ caching (`max-age=31536000`).
381
+
382
+ 3. **Update Layout Contract Defaults**
383
+
384
+ Replace raw inline objects with `fragmentDefault()`:
385
+
386
+ ```ts
387
+ defaults: {
388
+ header: fragmentDefault('navbar'),
389
+ footer: fragmentDefault('site-footer'),
390
+ }
391
+ ```
392
+
393
+ 4. **General Migration Steps**
394
+
395
+ Apply all general migration steps (SDK version, Zod version, Storybook
396
+ upgrade, etc.).
397
+
398
+ ---
399
+
400
+ ### Website-v2 (gradial/website-v2)
401
+
402
+ **Golden reference (Next.js).** Needs only minor updates.
403
+
404
+ #### Required Changes
405
+
406
+ 1. **SDK Version Bump**
407
+
408
+ Bump from `^0.1.28` to `^0.1.31`. Test on gradial-dev first (has the most
409
+ content/components and potential for breakage).
410
+
411
+ 2. **General Migration Steps**
412
+
413
+ Apply all general migration steps. Website-v2 is already close to the canon;
414
+ the main task is updating to the latest SDK and conformance preset.
415
+
416
+ ---
417
+
418
+ ### Website-v2-Astro (gradial/website-v2-astro)
419
+
420
+ **MISNAMED — Actually Next.js.** Despite the name, this repo is a Next.js 15
421
+ app, not Astro. It mirrors website-v2's structure.
422
+
423
+ #### Required Changes
424
+
425
+ Same as website-v2 (it's a near-clone). Treat as a Next.js repo, not an Astro
426
+ repo.
427
+
428
+ ---
429
+
430
+ ## Conformance Validation
431
+
432
+ After migration, validate conformance using the SDK testing preset:
433
+
434
+ ```ts
435
+ import { conformancePreset } from '@gradial/aci/testing';
436
+
437
+ conformancePreset({
438
+ framework: 'next',
439
+ siteId: 'your-tenant-id',
440
+ });
441
+ ```
442
+
443
+ The preset validates (default checks):
444
+ - `config` -- `withAci()` present in config
445
+ - `registry` -- `createRegistry()` usage (no hand-rolled maps)
446
+ - `pageEntry` -- `createPage(registry)` usage in catch-all route
447
+ - `middleware` -- `createGradialMiddleware({ siteId })` with correct siteId
448
+ - `noFileContentProvider` -- no direct `FileContentProvider` imports in app code
449
+ - `noCustomContent` -- no `src/lib/content.ts` shim
450
+ - `noLegacyAssetRoute` -- no `[releaseId]` segment in asset route path
451
+ - `sdkVersion` -- SDK version >= 0.1.31
452
+
453
+ Optional (disabled by default, shell out):
454
+ - `buildSucceeds` -- full build completes
455
+ - `contentCompile` -- content compilation succeeds
456
+
457
+ ---
458
+
459
+ ## Spec Ambiguities (Veto Pending)
460
+
461
+ The following interpretations from the spec may need clarification:
462
+
463
+ | Ruling | Interpretation | Risk |
464
+ |--------|----------------|------|
465
+ | 7 (Asset routes) | `withAci()` for Next.js; CloudFront for Astro. Asset-route export stays transitional. | May want immediate deletion |
466
+ | 11 (Content compile) | npm-wrapped Go CLI: `npx @gradial/aci content compile` | May mean new TS-native command |
467
+ | 12 (Slug shape) | `[[...slug]]` optional — file-provider dev needs empty slug for `/` | N/A (resolved) |
468
+ | 15 (Zod version) | zod 4.x caret range | May mean pin exact minor |
469
+
470
+ > **Note:** If any of these interpretations are incorrect, the implementing
471
+ > workstreams (A/B/C) will also need guidance. Flag ambiguities early.
@@ -0,0 +1,15 @@
1
+ {
2
+ "$type": "site",
3
+ "id": "{{siteName}}",
4
+ "status": "published",
5
+ "title": "{{siteName}}",
6
+ "domain": "localhost",
7
+ "defaultLocale": "en-us",
8
+ "locales": ["en-us"],
9
+ "overlayResolution": ["locale"],
10
+ "brandHref": "/",
11
+ "seo": {
12
+ "title": "{{siteName}}",
13
+ "description": "An ACI site."
14
+ }
15
+ }