@gradial/aci 0.1.20-preview.2 → 0.1.20
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.
- package/README.md +48 -110
- package/bin/{aci → aci-darwin-arm64} +0 -0
- package/bin/aci.js +99 -6
- package/dist/assets/index.d.ts +3 -3
- package/dist/assets/index.js +3 -3
- package/dist/astro/index.d.ts +14 -3
- package/dist/astro/index.js +24 -5
- package/dist/content/assets.d.ts +17 -0
- package/dist/content/assets.js +65 -0
- package/dist/content/index.d.ts +2 -0
- package/dist/content/index.js +2 -0
- package/dist/content/provider.d.ts +19 -0
- package/dist/content/provider.js +7 -0
- package/dist/content/resolve-slots.d.ts +9 -0
- package/dist/content/resolve-slots.js +24 -0
- package/dist/content/types.d.ts +1 -1
- package/dist/define-component.d.ts +1 -2
- package/dist/define-component.js +73 -11
- package/dist/define-layout.d.ts +2 -0
- package/dist/define-layout.js +3 -0
- package/dist/dev/content-watch.d.ts +37 -0
- package/dist/dev/content-watch.js +154 -0
- package/dist/dev/index.d.ts +24 -43
- package/dist/dev/index.js +4 -169
- package/dist/index.d.ts +14 -5
- package/dist/index.js +8 -8
- package/dist/next/asset-route.d.ts +2 -0
- package/dist/next/asset-route.js +12 -4
- package/dist/next/config.d.ts +2 -2
- package/dist/next/config.js +6 -6
- package/dist/next/content-watch.js +6 -1
- package/dist/next/index.d.ts +2 -2
- package/dist/next/index.js +2 -2
- package/dist/next/middleware.d.ts +7 -3
- package/dist/next/middleware.js +12 -7
- package/dist/next/page.d.ts +16 -15
- package/dist/next/page.js +30 -10
- package/dist/next/render.d.ts +5 -0
- package/dist/next/render.js +17 -0
- package/dist/next/root-layout.d.ts +4 -0
- package/dist/next/root-layout.js +5 -0
- package/dist/next/server.d.ts +14 -13
- package/dist/next/server.js +13 -3
- package/dist/providers/file.d.ts +2 -0
- package/dist/providers/file.js +61 -1
- package/dist/providers/s3.d.ts +1 -0
- package/dist/providers/s3.js +14 -1
- package/dist/react/Media.d.ts +24 -0
- package/dist/react/Media.js +30 -0
- package/dist/react/Picture.d.ts +12 -0
- package/dist/react/Picture.js +11 -0
- package/dist/react/VideoPlayer.d.ts +13 -0
- package/dist/react/{GradialVideoPlayer.js → VideoPlayer.js} +6 -5
- package/dist/react/index.d.ts +333 -3
- package/dist/react/index.js +89 -3
- package/dist/registry.d.ts +15 -0
- package/dist/registry.js +10 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.js +15 -2
- package/dist/types/block-ref.d.ts +26 -0
- package/dist/types/block-ref.js +28 -0
- package/dist/types/component.d.ts +36 -41
- package/dist/types/config.d.ts +1 -2
- package/dist/types/image.d.ts +33 -20
- package/dist/types/image.js +42 -50
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/link.d.ts +38 -0
- package/dist/types/link.js +58 -0
- package/dist/types/media.d.ts +16 -35
- package/dist/types/media.js +15 -84
- package/dist/types/page.d.ts +1 -2
- package/dist/types/render-mode.d.ts +0 -1
- package/dist/types/renderer.d.ts +1 -1
- package/dist/types/rich-text.d.ts +40 -0
- package/dist/types/rich-text.js +39 -0
- package/dist/types/video.d.ts +7 -13
- package/dist/types/video.js +3 -3
- package/package.json +32 -74
- package/src/cli/compile-registry.mjs +236 -59
- package/src/cli/validate-content.mjs +38 -75
- package/src/types/image.ts +79 -60
- package/templates/astro/template/.aci.yaml +27 -0
- package/templates/astro/template/.storybook/main.ts +79 -0
- package/templates/astro/template/.storybook/manager.ts +6 -0
- package/templates/astro/template/.storybook/preview.ts +33 -0
- package/templates/astro/template/astro.config.mjs +37 -0
- package/templates/astro/template/package.json.tmpl +41 -0
- package/templates/astro/template/src/cms/contracts/layouts/index.ts +18 -0
- package/templates/astro/template/src/cms/registry.ts +28 -0
- package/templates/astro/template/src/components/chrome/Footer.astro +107 -0
- package/templates/astro/template/src/components/chrome/Header.astro +94 -0
- package/templates/astro/template/src/components/elements/AtomicBlock.astro +15 -0
- package/templates/astro/template/src/components/elements/AtomicBlocks.astro +16 -0
- package/templates/astro/template/src/components/elements/Button.astro +51 -0
- package/templates/astro/template/src/components/elements/ButtonBlock.astro +25 -0
- package/templates/astro/template/src/components/elements/Container.astro +11 -0
- package/templates/astro/template/src/components/elements/Icon.astro +31 -0
- package/templates/astro/template/src/components/elements/Logo.astro +29 -0
- package/templates/astro/template/src/components/elements/RemoteImage.astro +41 -0
- package/templates/astro/template/src/components/sections/ContainerSection.astro +32 -0
- package/templates/astro/template/src/components/sections/FeatureCard.astro +23 -0
- package/templates/astro/template/src/components/sections/FeatureGrid.astro +35 -0
- package/templates/astro/template/src/components/sections/HomeHero.astro +51 -0
- package/templates/astro/template/src/components/sections/StatsBar.astro +33 -0
- package/templates/astro/template/src/design-system/styles.css +399 -0
- package/templates/astro/template/src/design-system/tokens/colors.css +60 -0
- package/templates/astro/template/src/design-system/tokens/components.css +69 -0
- package/templates/astro/template/src/design-system/tokens/index.css +6 -0
- package/templates/astro/template/src/design-system/tokens/radii.css +5 -0
- package/templates/astro/template/src/design-system/tokens/shadows.css +5 -0
- package/templates/astro/template/src/design-system/tokens/spacing.css +13 -0
- package/templates/astro/template/src/design-system/tokens/typography.css +24 -0
- package/templates/astro/template/src/env.d.ts +1 -0
- package/templates/astro/template/src/fixtures/atomicBlocks.ts +33 -0
- package/templates/astro/template/src/fixtures/composition.ts +31 -0
- package/templates/astro/template/src/fixtures/index.ts +17 -0
- package/templates/astro/template/src/fixtures/sections.ts +39 -0
- package/templates/astro/template/src/layouts/SiteLayout.astro +30 -0
- package/templates/astro/template/src/pages/[...slug].astro +61 -0
- package/templates/astro/template/src/render/RenderPage.astro +66 -0
- package/templates/astro/template/src/stories/CompositionStoryFrame.astro +15 -0
- package/templates/astro/template/src/stories/HomeHero.stories.ts +24 -0
- package/templates/astro/template/src/stories/SectionStoryFrame.astro +16 -0
- package/templates/astro/template/src/stories/StoryDocFrame.astro +31 -0
- package/templates/astro/template/tsconfig.json +11 -0
- package/templates/common/.agents/AGENTS.md +37 -0
- package/templates/common/.agents/skills/aci/SKILL.md +162 -0
- package/templates/common/.agents/skills/authoring/SKILL.md +590 -0
- package/templates/common/.agents/skills/authoring/references/block-registry.md +151 -0
- package/templates/common/.agents/skills/authoring/references/contracts.md +153 -0
- package/templates/common/.agents/skills/authoring/references/design-system.md +171 -0
- package/templates/common/.agents/skills/authoring/references/image-assets.md +73 -0
- package/templates/common/.agents/skills/authoring/references/repo-structure.md +157 -0
- package/templates/common/.agents/skills/migration/SKILL.md +160 -0
- package/templates/common/.agents/skills/migration/references/analyzing-source.md +215 -0
- package/templates/common/.agents/skills/migration/references/component-migration.md +200 -0
- package/templates/common/.agents/skills/migration/references/selector-map.md +31 -0
- package/templates/common/.content/.gradial-dam/blobs/sha256/7f/bf/7fbfc7fe230ff12688c496f660119e41d9725c4504d82e310c15665c73c09292 +0 -0
- package/templates/common/.content/.gradial-dam/blobs/sha256/a0/f5/a0f506689de9b15a7055f70103a3f02cd3ba105771ae2de6754df2ede1210d3a +0 -0
- package/templates/common/.content/.gradial-dam/blobs/sha256/dd/b7/ddb7e9642be08317bcd4b2bd26b58ba8d0d34dd7239e03e684a4f3634fa3845b +0 -0
- package/templates/common/.content/.gradial-dam/blobs/sha256/fb/32/fb3260e8286ea8231ea1c77b9c0be92d6edd9026565731eb14cfdcfd6b1b75b3 +0 -0
- package/templates/common/.content/config/site.json +192 -0
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/asset.json +9 -0
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/editorial.json +14 -0
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/governance.json +23 -0
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/versions/v0001.json +37 -0
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/asset.json +9 -0
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/editorial.json +14 -0
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/governance.json +23 -0
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/versions/v0001.json +37 -0
- package/templates/common/.content/fragments/navbar/_index.json +25 -0
- package/templates/common/.content/fragments/site-footer/_index.json +62 -0
- package/templates/common/.content/pages/about/_index.json +51 -0
- package/templates/common/.content/pages/home/_index.json +112 -0
- package/templates/common/.content/schemas/site.schema.json +123 -0
- package/templates/common/README.md.tmpl +45 -0
- package/templates/common/contracts/components/button.contract.ts +19 -0
- package/templates/common/contracts/components/container.contract.ts +19 -0
- package/templates/common/contracts/components/featureCard.contract.ts +15 -0
- package/templates/common/contracts/components/featureGrid.contract.ts +16 -0
- package/templates/common/contracts/components/homeHero.contract.ts +36 -0
- package/templates/common/contracts/components/index.ts +21 -0
- package/templates/common/contracts/components/siteFooter.contract.ts +45 -0
- package/templates/common/contracts/components/siteNavigation.contract.ts +30 -0
- package/templates/common/contracts/components/statsBar.contract.ts +19 -0
- package/templates/common/design-system/styles.css +346 -0
- package/templates/common/design-system/tokens/colors.css +110 -0
- package/templates/common/design-system/tokens/components.css +86 -0
- package/templates/common/design-system/tokens/index.css +6 -0
- package/templates/common/design-system/tokens/radii.css +5 -0
- package/templates/common/design-system/tokens/shadows.css +9 -0
- package/templates/common/design-system/tokens/spacing.css +26 -0
- package/templates/common/design-system/tokens/typography.css +32 -0
- package/templates/common/eslint-rules/no-raw-tailwind-values.js +127 -0
- package/templates/common/public/assets/Dwire-model.png +0 -0
- package/templates/common/public/assets/bare-metal-logo.png +0 -0
- package/templates/common/public/assets/meridian-logo.svg +8 -0
- package/templates/common/public/assets/seattle.avif +0 -0
- package/templates/nextjs/template/.aci.yaml +25 -0
- package/templates/nextjs/template/.storybook/main.ts +13 -0
- package/templates/nextjs/template/.storybook/preview.ts +34 -0
- package/templates/nextjs/template/eslint.config.mjs +36 -0
- package/templates/nextjs/template/next-env.d.ts +6 -0
- package/templates/nextjs/template/next.config.ts +8 -0
- package/templates/nextjs/template/package.json.tmpl +46 -0
- package/templates/nextjs/template/postcss.config.mjs +7 -0
- package/templates/nextjs/template/src/app/[[...slug]]/page.tsx +8 -0
- package/templates/nextjs/template/src/app/gradial/assets/[releaseId]/[...path]/route.ts +31 -0
- package/templates/nextjs/template/src/app/layout.tsx +29 -0
- package/templates/nextjs/template/src/cms/contracts/components/button.contract.ts +19 -0
- package/templates/nextjs/template/src/cms/contracts/components/container.contract.ts +20 -0
- package/templates/nextjs/template/src/cms/contracts/components/homeHero.contract.ts +36 -0
- package/templates/nextjs/template/src/cms/contracts/components/index.ts +15 -0
- package/templates/nextjs/template/src/cms/contracts/components/siteFooter.contract.ts +45 -0
- package/templates/nextjs/template/src/cms/contracts/components/siteNavigation.contract.ts +30 -0
- package/templates/nextjs/template/src/cms/contracts/layouts/index.ts +16 -0
- package/templates/nextjs/template/src/cms/registry.ts +28 -0
- package/templates/nextjs/template/src/components/chrome/Footer.tsx +86 -0
- package/templates/nextjs/template/src/components/chrome/Header.tsx +74 -0
- package/templates/nextjs/template/src/components/elements/AtomicBlock.tsx +13 -0
- package/templates/nextjs/template/src/components/elements/Button.tsx +40 -0
- package/templates/nextjs/template/src/components/elements/ButtonBlock.tsx +18 -0
- package/templates/nextjs/template/src/components/elements/Container.tsx +14 -0
- package/templates/nextjs/template/src/components/elements/Icon.tsx +29 -0
- package/templates/nextjs/template/src/components/elements/Logo.tsx +38 -0
- package/templates/nextjs/template/src/components/sections/ContainerSection.tsx +28 -0
- package/templates/nextjs/template/src/components/sections/FeatureCard.tsx +25 -0
- package/templates/nextjs/template/src/components/sections/FeatureGrid.tsx +36 -0
- package/templates/nextjs/template/src/components/sections/HomeHero.tsx +62 -0
- package/templates/nextjs/template/src/components/sections/StatsBar.tsx +30 -0
- package/templates/nextjs/template/src/middleware.ts +4 -0
- package/templates/nextjs/template/src/stories/Button.stories.tsx +38 -0
- package/templates/nextjs/template/src/stories/Header.stories.tsx +30 -0
- package/templates/nextjs/template/src/stories/HomeHero.stories.tsx +33 -0
- package/templates/nextjs/template/tsconfig.json +23 -0
- package/templates/nextjs/template/vercel.json +6 -0
- package/dist/content/cache.test.d.ts +0 -1
- package/dist/content/cache.test.js +0 -86
- package/dist/content/contract.d.ts +0 -83
- package/dist/content/contract.js +0 -104
- package/dist/content/tailwind-validator.d.ts +0 -6
- package/dist/content/tailwind-validator.js +0 -31
- package/dist/content/validation.d.ts +0 -108
- package/dist/content/validation.js +0 -184
- package/dist/next/preview-banner.d.ts +0 -4
- package/dist/next/preview-banner.js +0 -51
- package/dist/next/preview.d.ts +0 -7
- package/dist/next/preview.js +0 -37
- package/dist/react/GradialMedia.d.ts +0 -24
- package/dist/react/GradialMedia.js +0 -31
- package/dist/react/GradialPicture.d.ts +0 -14
- package/dist/react/GradialPicture.js +0 -30
- package/dist/react/GradialVideoPlayer.d.ts +0 -13
- package/dist/sveltekit/index.d.ts +0 -23
- package/dist/sveltekit/index.js +0 -62
- package/dist/sveltekit/preview.d.ts +0 -5
- package/dist/sveltekit/preview.js +0 -33
- package/src/cli/verify-renderer.mjs +0 -73
- package/src/types/component.ts +0 -59
- package/src/types/config.ts +0 -38
- package/src/types/data.ts +0 -47
- package/src/types/index.ts +0 -10
- package/src/types/layout.ts +0 -29
- package/src/types/media.ts +0 -125
- package/src/types/page.ts +0 -48
- package/src/types/render-mode.ts +0 -18
- package/src/types/renderer.ts +0 -83
- package/src/types/video.ts +0 -66
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: authoring
|
|
3
|
+
description: Author content and build components for ACI sites. Use when creating or editing content JSON, adding components, writing contracts, wiring the component registry, working with design tokens, building Storybook stories, or understanding the contract/runtime split.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Authoring — Content and Components
|
|
7
|
+
|
|
8
|
+
## Content Authoring
|
|
9
|
+
|
|
10
|
+
### Content Locations
|
|
11
|
+
|
|
12
|
+
| Path | Purpose |
|
|
13
|
+
|------|---------|
|
|
14
|
+
| `.content/config/site.json` | Site metadata, SEO defaults |
|
|
15
|
+
| `.content/pages/{slug}/_index.json` | Page content (slug maps to route) |
|
|
16
|
+
| `.content/fragments/{id}/_index.json` | Shared fragments (navbar, footer — used by layout defaults) |
|
|
17
|
+
| `.aci/schemas/page.schema.json` | Page validation schema (auto-generated from contracts) |
|
|
18
|
+
|
|
19
|
+
### Page Structure
|
|
20
|
+
|
|
21
|
+
Every page JSON requires:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"$type": "page",
|
|
26
|
+
"id": "unique-page-id",
|
|
27
|
+
"status": "published",
|
|
28
|
+
"layout": "default",
|
|
29
|
+
"metadata": {
|
|
30
|
+
"title": "Page Title",
|
|
31
|
+
"description": "Meta description"
|
|
32
|
+
},
|
|
33
|
+
"regions": {
|
|
34
|
+
"main": []
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The `layout` field is required — it determines which slots the page can
|
|
40
|
+
fill. The `default` layout provides `header`/`main`/`footer` slots with
|
|
41
|
+
fragment defaults for chrome (navbar, footer). Pages typically only fill
|
|
42
|
+
`regions.main`; chrome comes from the layout's fragment defaults.
|
|
43
|
+
|
|
44
|
+
### Route Mapping
|
|
45
|
+
|
|
46
|
+
Pages map to URL routes by their folder path under `/pages/`:
|
|
47
|
+
|
|
48
|
+
| Page Path | Route |
|
|
49
|
+
|-----------|-------|
|
|
50
|
+
| `/pages/home/_index.json` | `/` |
|
|
51
|
+
| `/pages/about/_index.json` | `/about` |
|
|
52
|
+
| `/pages/products/_index.json` | `/products` |
|
|
53
|
+
| `/pages/products/shoes/_index.json` | `/products/shoes` |
|
|
54
|
+
|
|
55
|
+
The `home` slug is special-cased to route `/`. All other slugs map directly.
|
|
56
|
+
|
|
57
|
+
### Content Rules
|
|
58
|
+
|
|
59
|
+
- Never hard-code content inside component code. All content lives in JSON.
|
|
60
|
+
- Migrate copy verbatim. Do not paraphrase, summarize, or invent.
|
|
61
|
+
- Global elements (header, footer, nav) are CMS-managed fragments in
|
|
62
|
+
`.content/fragments/`, rendered via layout fragment defaults. They are
|
|
63
|
+
not hardcoded in framework layout files.
|
|
64
|
+
- Use `$ref:` prefixes for cross-references between content documents.
|
|
65
|
+
|
|
66
|
+
### Block Structure
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"id": "hero-section",
|
|
71
|
+
"component": "home_hero",
|
|
72
|
+
"props": {
|
|
73
|
+
"headline": "Ship content updates across every framework.",
|
|
74
|
+
"ctaLabel": "Get started",
|
|
75
|
+
"ctaHref": "/docs"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- `component` must match a contract `id` exactly.
|
|
81
|
+
- `props` must validate against the contract's Zod schema.
|
|
82
|
+
- `id` is unique within the page.
|
|
83
|
+
|
|
84
|
+
### Nested Blocks
|
|
85
|
+
|
|
86
|
+
Components that accept child blocks use `blockRef()` in their Zod schema
|
|
87
|
+
instead of a separate `slots` property. Nested blocks live inside `props`,
|
|
88
|
+
not in a sibling `slots` field.
|
|
89
|
+
|
|
90
|
+
**Contract:**
|
|
91
|
+
```ts
|
|
92
|
+
import { defineComponentContract, blockRef } from '@gradial/aci';
|
|
93
|
+
import { z } from 'zod';
|
|
94
|
+
|
|
95
|
+
export const schema = z.object({
|
|
96
|
+
theme: z.enum(['auto', 'light', 'dark']).optional(),
|
|
97
|
+
className: z.string().optional(),
|
|
98
|
+
content: blockRef({
|
|
99
|
+
allowed: ['button', 'feature_grid', 'stats_bar'],
|
|
100
|
+
multiple: true,
|
|
101
|
+
}).optional(),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
export const containerContract = defineComponentContract({
|
|
105
|
+
id: 'container',
|
|
106
|
+
props: schema,
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Content JSON:**
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"id": "cta-section",
|
|
114
|
+
"component": "container",
|
|
115
|
+
"props": {
|
|
116
|
+
"className": "space-y-4",
|
|
117
|
+
"content": [
|
|
118
|
+
{
|
|
119
|
+
"id": "primary-cta",
|
|
120
|
+
"component": "button",
|
|
121
|
+
"props": {
|
|
122
|
+
"label": "Get started",
|
|
123
|
+
"href": "#start",
|
|
124
|
+
"variant": "primary"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"id": "secondary-cta",
|
|
129
|
+
"component": "button",
|
|
130
|
+
"props": {
|
|
131
|
+
"label": "Learn more",
|
|
132
|
+
"href": "#learn",
|
|
133
|
+
"variant": "secondary"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The `blockRef()` function:
|
|
142
|
+
- Takes `{ allowed: string[], multiple?: boolean }`.
|
|
143
|
+
- `allowed` restricts which component IDs can be nested.
|
|
144
|
+
- `multiple: true` (default) allows an array of blocks.
|
|
145
|
+
- The compiler scans the schema for `blockRef` fields and auto-discovers
|
|
146
|
+
block slots — no separate `blockSlots` declaration needed.
|
|
147
|
+
|
|
148
|
+
### Button Block
|
|
149
|
+
|
|
150
|
+
The `button` component is a common atomic block:
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"id": "cta-button",
|
|
155
|
+
"component": "button",
|
|
156
|
+
"props": {
|
|
157
|
+
"label": "Get started",
|
|
158
|
+
"href": "https://example.com",
|
|
159
|
+
"variant": "primary",
|
|
160
|
+
"icon": "arrow_forward",
|
|
161
|
+
"iconPosition": "after",
|
|
162
|
+
"newWindow": true
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
- `label` and `href` are required.
|
|
168
|
+
- `variant`: `primary` | `secondary` | `outline` | `tertiary`
|
|
169
|
+
- `icon`: a registered icon name.
|
|
170
|
+
- `iconPosition`: `before` | `after`
|
|
171
|
+
- `newWindow`: boolean for `target="_blank"`.
|
|
172
|
+
|
|
173
|
+
### Site Config
|
|
174
|
+
|
|
175
|
+
Global elements (navigation, footer, branding, SEO) live in
|
|
176
|
+
`.content/config/site.json`:
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"$type": "site",
|
|
181
|
+
"id": "meridian",
|
|
182
|
+
"status": "published",
|
|
183
|
+
"title": "Meridian",
|
|
184
|
+
"domain": "www.meridian.local",
|
|
185
|
+
"defaultLocale": "en-us",
|
|
186
|
+
"locales": ["en-us"],
|
|
187
|
+
"brandHref": "/",
|
|
188
|
+
"brandLogo": {
|
|
189
|
+
"src": "/assets/meridian-logo.svg",
|
|
190
|
+
"alt": "Meridian"
|
|
191
|
+
},
|
|
192
|
+
"seo": {
|
|
193
|
+
"title": "Meridian",
|
|
194
|
+
"description": "Global satellite internet from anywhere on Earth.",
|
|
195
|
+
"siteName": "Meridian"
|
|
196
|
+
},
|
|
197
|
+
"navigation": [
|
|
198
|
+
{ "id": "home", "label": "Home", "href": "/" },
|
|
199
|
+
{ "id": "about", "label": "About", "href": "/about/" }
|
|
200
|
+
],
|
|
201
|
+
"footer": {
|
|
202
|
+
"columns": [...],
|
|
203
|
+
"social": [...],
|
|
204
|
+
"legalLinks": [...],
|
|
205
|
+
"copyright": "Copyright (c) 2026 ..."
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### ID Conventions
|
|
211
|
+
|
|
212
|
+
- Every block must have a unique `id` within the page.
|
|
213
|
+
- Use kebab-case: `hero-heading`, `feature-cta`, `card-1-image`.
|
|
214
|
+
- Prefix with section context for clarity: `callouts-heading`, `demo-cta`.
|
|
215
|
+
|
|
216
|
+
### className Validation
|
|
217
|
+
|
|
218
|
+
The `className` field accepts any valid Tailwind CSS utility class, as
|
|
219
|
+
long as every value ultimately derives from the design system's CSS custom
|
|
220
|
+
properties. The validator rejects raw hex/px/rem bracket values (e.g.,
|
|
221
|
+
`[#fff]`, `[12px]`) but allows `[var(--...)]` bracket syntax for CSS
|
|
222
|
+
variable references.
|
|
223
|
+
|
|
224
|
+
### Image References
|
|
225
|
+
|
|
226
|
+
Use DAM asset references for images:
|
|
227
|
+
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"image": {
|
|
231
|
+
"$type": "dam.assetRef",
|
|
232
|
+
"assetId": "ast_site_home_hero",
|
|
233
|
+
"alt": "Hero illustration"
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The compiler resolves these to Gradial image objects with primary URLs,
|
|
239
|
+
sources, and dimensions.
|
|
240
|
+
|
|
241
|
+
## Component Development
|
|
242
|
+
|
|
243
|
+
### Contract / Runtime Split
|
|
244
|
+
|
|
245
|
+
Every CMS-managed component has two parts:
|
|
246
|
+
|
|
247
|
+
1. **Contract** (`src/cms/contracts/components/{name}.contract.ts`) —
|
|
248
|
+
compile-time metadata: component name, Zod schema, render modes, image
|
|
249
|
+
slots. Imports only `@gradial/aci` and `zod`.
|
|
250
|
+
|
|
251
|
+
2. **Runtime** (`src/components/`) — the actual framework component
|
|
252
|
+
(Astro or React). May import contract types for prop typing.
|
|
253
|
+
|
|
254
|
+
The ACI compiler imports only contracts. Contracts must never import
|
|
255
|
+
framework components, CSS, browser globals, or runtime modules.
|
|
256
|
+
|
|
257
|
+
Only Next.js and Astro are supported. SvelteKit support has been removed.
|
|
258
|
+
|
|
259
|
+
### Adding a New Component
|
|
260
|
+
|
|
261
|
+
#### 1. Create the contract
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
// src/cms/contracts/components/featureShowcase.contract.ts
|
|
265
|
+
import { defineComponentContract, ImageSchema } from '@gradial/aci';
|
|
266
|
+
import { z } from 'zod';
|
|
267
|
+
|
|
268
|
+
export const schema = z.object({
|
|
269
|
+
headline: z.string().min(1),
|
|
270
|
+
description: z.string().optional(),
|
|
271
|
+
image: ImageSchema,
|
|
272
|
+
features: z.array(z.object({
|
|
273
|
+
title: z.string().min(1),
|
|
274
|
+
description: z.string().min(1),
|
|
275
|
+
})).optional(),
|
|
276
|
+
ctaLabel: z.string().optional(),
|
|
277
|
+
ctaHref: z.string().optional(),
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
export type ContentProps = z.infer<typeof schema>;
|
|
281
|
+
|
|
282
|
+
export const featureShowcaseContract = defineComponentContract({
|
|
283
|
+
id: 'feature_showcase',
|
|
284
|
+
props: schema,
|
|
285
|
+
imageSlots: {
|
|
286
|
+
image: {
|
|
287
|
+
formats: ['image/avif', 'image/webp', 'image/jpeg'],
|
|
288
|
+
sizes: '50vw',
|
|
289
|
+
outputs: [
|
|
290
|
+
{ aspectRatio: '16:9', widths: [640, 960, 1280] },
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### 2. Export from the barrel
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
// src/cms/contracts/components/index.ts
|
|
301
|
+
import { featureShowcaseContract } from './featureShowcase.contract';
|
|
302
|
+
|
|
303
|
+
export default [
|
|
304
|
+
// ... existing contracts
|
|
305
|
+
featureShowcaseContract,
|
|
306
|
+
];
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
#### 3. Create the runtime component
|
|
310
|
+
|
|
311
|
+
**Astro:**
|
|
312
|
+
```astro
|
|
313
|
+
---
|
|
314
|
+
// src/components/sections/FeatureShowcase.astro
|
|
315
|
+
import { primaryImageSource } from '@gradial/aci';
|
|
316
|
+
import type { ContentProps } from '../../cms/contracts/components/featureShowcase.contract';
|
|
317
|
+
|
|
318
|
+
interface Props extends ContentProps {
|
|
319
|
+
className?: string;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const { headline, description, image, features, ctaLabel, ctaHref, className } = Astro.props;
|
|
323
|
+
---
|
|
324
|
+
<section class={`py-[var(--spacing-section-y)] ${className ?? ''}`}>
|
|
325
|
+
<div class="mx-auto max-w-[var(--max-width-container)] px-[var(--spacing-container)]">
|
|
326
|
+
<h2 class="text-[length:var(--text-size-h2)]">{headline}</h2>
|
|
327
|
+
{description && <p class="text-[length:var(--text-size-body-large)]">{description}</p>}
|
|
328
|
+
{image && <img src={primaryImageSource(image).src} alt={image.alt ?? ''} />}
|
|
329
|
+
{features?.map(f => (
|
|
330
|
+
<div>
|
|
331
|
+
<h3>{f.title}</h3>
|
|
332
|
+
<p>{f.description}</p>
|
|
333
|
+
</div>
|
|
334
|
+
))}
|
|
335
|
+
</div>
|
|
336
|
+
</section>
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**React (Next.js):**
|
|
340
|
+
```tsx
|
|
341
|
+
// src/components/sections/FeatureShowcase.tsx
|
|
342
|
+
import { primaryImageSource } from '@gradial/aci';
|
|
343
|
+
import type { ContentProps } from '../../cms/contracts/components/featureShowcase.contract';
|
|
344
|
+
|
|
345
|
+
type FeatureShowcaseProps = ContentProps & { className?: string };
|
|
346
|
+
|
|
347
|
+
export function FeatureShowcase({ headline, description, image, features, className }: FeatureShowcaseProps) {
|
|
348
|
+
return (
|
|
349
|
+
<section className={`py-[var(--spacing-section-y)] ${className ?? ''}`}>
|
|
350
|
+
<div className="mx-auto max-w-[var(--max-width-container)] px-[var(--spacing-container)]">
|
|
351
|
+
<h2 className="text-[length:var(--text-size-h2)]">{headline}</h2>
|
|
352
|
+
{description && <p className="text-[length:var(--text-size-body-large)]">{description}</p>}
|
|
353
|
+
{image && <img src={primaryImageSource(image).src} alt={image.alt ?? ''} />}
|
|
354
|
+
</div>
|
|
355
|
+
</section>
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
#### 4. Wire into the component registry
|
|
361
|
+
|
|
362
|
+
Add a `[contract, component]` tuple in `src/cms/registry.ts`:
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
import { createRegistry } from '@gradial/aci';
|
|
366
|
+
import { featureShowcaseContract } from './contracts/components/featureShowcase.contract';
|
|
367
|
+
import { FeatureShowcase } from '@/components/sections/FeatureShowcase';
|
|
368
|
+
|
|
369
|
+
export const registry = createRegistry([
|
|
370
|
+
// ... existing entries
|
|
371
|
+
[featureShowcaseContract, FeatureShowcase],
|
|
372
|
+
]);
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
See `references/block-registry.md` for the full registry pattern for each
|
|
376
|
+
framework.
|
|
377
|
+
|
|
378
|
+
#### 5. Add content
|
|
379
|
+
|
|
380
|
+
```json
|
|
381
|
+
// .content/pages/home/_index.json (inside regions.main)
|
|
382
|
+
{
|
|
383
|
+
"id": "features",
|
|
384
|
+
"component": "feature_showcase",
|
|
385
|
+
"props": {
|
|
386
|
+
"headline": "Why teams choose us",
|
|
387
|
+
"image": {
|
|
388
|
+
"$type": "dam.assetRef",
|
|
389
|
+
"assetId": "feature_image",
|
|
390
|
+
"alt": "Product dashboard"
|
|
391
|
+
},
|
|
392
|
+
"features": [
|
|
393
|
+
{ "title": "Fast", "description": "Sub-second page loads" },
|
|
394
|
+
{ "title": "Secure", "description": "SOC 2 compliant" }
|
|
395
|
+
]
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
#### 6. Verify
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
npm run aci:compile # Contract compiles
|
|
404
|
+
npm run aci:validate # Content validates
|
|
405
|
+
npm run typecheck # Types check
|
|
406
|
+
npm run build # Framework builds
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Contract Rules
|
|
410
|
+
|
|
411
|
+
- Import `@gradial/aci`, `zod`, and shared schema helpers only.
|
|
412
|
+
- Export a conventional `<camelName>Contract` value.
|
|
413
|
+
- Export `ContentProps` from `z.infer<typeof schema>`.
|
|
414
|
+
- Use `defineComponentContract({ id, props: schema })` — the `id` is the
|
|
415
|
+
contract name used in content JSON `component` fields and the registry.
|
|
416
|
+
- `imageSlots` must match all `ImageSchema` fields, including
|
|
417
|
+
nested dot paths like `items.image`.
|
|
418
|
+
- `renderModes` has been removed from the contract API. There is no
|
|
419
|
+
`renderModes` field on `defineComponentContract`.
|
|
420
|
+
|
|
421
|
+
### Composition Props
|
|
422
|
+
|
|
423
|
+
Props like `className`, slots, and rendered child nodes stay outside the
|
|
424
|
+
CMS contract. They are framework concerns, not content:
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
// Contract: only content props
|
|
428
|
+
export const schema = z.object({
|
|
429
|
+
headline: z.string(),
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// Runtime: content props + composition props
|
|
433
|
+
type Props = ContentProps & { className?: string };
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Import Rules
|
|
437
|
+
|
|
438
|
+
1. **Contract files** import only `@gradial/aci`, `zod`, and shared
|
|
439
|
+
schema helpers. No React, Astro, CSS, browser globals, or runtime
|
|
440
|
+
modules.
|
|
441
|
+
2. **Runtime components** may import contract types and framework code.
|
|
442
|
+
3. **Registry files** (`src/cms/registry.ts`) import both contracts and
|
|
443
|
+
runtime components — they are the wiring point.
|
|
444
|
+
4. Use `@gradial/aci` as the SDK package. Do not use `@baremetal/runtime`.
|
|
445
|
+
|
|
446
|
+
### Layout Contracts
|
|
447
|
+
|
|
448
|
+
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:
|
|
451
|
+
|
|
452
|
+
```ts
|
|
453
|
+
// src/cms/contracts/layouts/index.ts
|
|
454
|
+
export default [
|
|
455
|
+
{
|
|
456
|
+
name: 'default',
|
|
457
|
+
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
|
|
461
|
+
],
|
|
462
|
+
defaults: {
|
|
463
|
+
header: [{ kind: 'fragment-ref', fragmentId: 'navbar', inline: true }],
|
|
464
|
+
footer: [{ kind: 'fragment-ref', fragmentId: 'site-footer', inline: true }],
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
];
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Fragment content lives in `.content/fragments/{id}/_index.json` and has
|
|
471
|
+
a `component` + `props` just like page blocks. Chrome components (Header,
|
|
472
|
+
Footer) are registered in the registry alongside page components.
|
|
473
|
+
|
|
474
|
+
The `layout` field in page JSON is **required** — there is no implicit
|
|
475
|
+
default. Every page must declare which layout it uses.
|
|
476
|
+
|
|
477
|
+
## Component Tiers
|
|
478
|
+
|
|
479
|
+
Three directories, no more:
|
|
480
|
+
|
|
481
|
+
| Tier | Directory | Purpose |
|
|
482
|
+
|------|-----------|---------|
|
|
483
|
+
| elements | `src/components/elements/` | Small reusable UI + atomic content blocks |
|
|
484
|
+
| sections | `src/components/sections/` | Page-level section components |
|
|
485
|
+
| chrome | `src/components/chrome/` | Site chrome (Header, Footer, Nav) |
|
|
486
|
+
|
|
487
|
+
Do not create additional tiers (molecules, patterns, atoms as separate
|
|
488
|
+
directories). Three directories are sufficient.
|
|
489
|
+
|
|
490
|
+
## Design System
|
|
491
|
+
|
|
492
|
+
### Design Tokens
|
|
493
|
+
|
|
494
|
+
Tokens are CSS custom properties in `@theme` blocks under
|
|
495
|
+
`src/design-system/tokens/`. Tailwind v4 reads them and auto-generates
|
|
496
|
+
utility classes.
|
|
497
|
+
|
|
498
|
+
| File | Scope |
|
|
499
|
+
|------|-------|
|
|
500
|
+
| `colors.css` | Brand colors, semantic tokens, surfaces, borders |
|
|
501
|
+
| `typography.css` | Font families, sizes, weights, line heights |
|
|
502
|
+
| `spacing.css` | Spacing scale, container padding, section spacing |
|
|
503
|
+
| `radii.css` | Border radius values |
|
|
504
|
+
| `shadows.css` | Card and overlay shadows |
|
|
505
|
+
| `components.css` | Component-level tokens (buttons, header, footer) |
|
|
506
|
+
| `index.css` | Barrel import for all token files |
|
|
507
|
+
|
|
508
|
+
### Using Tokens
|
|
509
|
+
|
|
510
|
+
**Direct Tailwind utilities (preferred):**
|
|
511
|
+
- Color: `text-fg`, `text-fg-muted`, `bg-surface-default`, `border-edge`
|
|
512
|
+
- Radii: `rounded-card`, `rounded-button`
|
|
513
|
+
- Shadows: `shadow-card-default`
|
|
514
|
+
- Standard spacing: `px-4`, `gap-6`, `w-24`, `max-w-3xl`
|
|
515
|
+
|
|
516
|
+
**Bracket syntax (for tokens without direct utilities):**
|
|
517
|
+
- Gradients: `bg-[image:var(--gradient-cta-dark)]`
|
|
518
|
+
- Typography: `text-[length:var(--text-size-body)]`, `font-[var(--font-weight-semibold)]`
|
|
519
|
+
- Spacing tokens: `py-[var(--spacing-section-y)]`, `p-[var(--spacing-card)]`
|
|
520
|
+
|
|
521
|
+
### Forbidden Values
|
|
522
|
+
|
|
523
|
+
**No raw hex colors**: `text-[#8b72ff]`, `bg-[#f6f6f7]`
|
|
524
|
+
**No raw pixel values**: `p-[12px]`, `w-[200px]`, `gap-[24px]`
|
|
525
|
+
**No raw rem values**: `text-[1.25rem]`, `p-[0.75rem]`
|
|
526
|
+
**No Tailwind named colors**: `text-gray-500`, `bg-purple-400`
|
|
527
|
+
|
|
528
|
+
Use CSS variable tokens instead. If a token doesn't exist, add it to the
|
|
529
|
+
appropriate `tokens/*.css` file. The ESLint rule `no-raw-tailwind-values`
|
|
530
|
+
enforces this in component code.
|
|
531
|
+
|
|
532
|
+
### Theme System
|
|
533
|
+
|
|
534
|
+
Three theme classes control semantic token resolution:
|
|
535
|
+
|
|
536
|
+
| Class | Use Case |
|
|
537
|
+
|-------|----------|
|
|
538
|
+
| `theme-auto` | Normal page content following browser light/dark preference |
|
|
539
|
+
| `theme-dark` | Fixed dark surfaces (header, footer, hero) |
|
|
540
|
+
| `theme-accent` | CTA-style accent surfaces |
|
|
541
|
+
|
|
542
|
+
### Adding New Tokens
|
|
543
|
+
|
|
544
|
+
1. Add the token to the appropriate `tokens/*.css` file inside `@theme`:
|
|
545
|
+
```css
|
|
546
|
+
@theme {
|
|
547
|
+
--color-brand-500: #6b5ce7;
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
2. Reference it from component code.
|
|
551
|
+
3. Tailwind v4 will auto-generate a utility if the token name matches a
|
|
552
|
+
Tailwind namespace.
|
|
553
|
+
|
|
554
|
+
## Storybook
|
|
555
|
+
|
|
556
|
+
Every ACI frontend repo should have Storybook configured. Stories live
|
|
557
|
+
under `src/stories/` and follow the framework's Storybook setup.
|
|
558
|
+
|
|
559
|
+
```bash
|
|
560
|
+
npm run storybook # Dev mode
|
|
561
|
+
npm run build:storybook # Build all stories
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
## Deep Dive: Component Registry
|
|
565
|
+
|
|
566
|
+
Read `references/block-registry.md` for the `createRegistry` pattern,
|
|
567
|
+
framework-specific usage, and registry rules.
|
|
568
|
+
|
|
569
|
+
## Deep Dive: Image Slots and Assets
|
|
570
|
+
|
|
571
|
+
Read `references/image-assets.md` for `ImageSchema` usage, image
|
|
572
|
+
slot configuration, nested dot paths, and runtime image rendering rules.
|
|
573
|
+
|
|
574
|
+
## Deep Dive: Contracts
|
|
575
|
+
|
|
576
|
+
Read `references/contracts.md` for the full contract file structure,
|
|
577
|
+
`defineComponentContract` API, `.aci.yaml` config reference, plain
|
|
578
|
+
layout contract objects, and contract barrel patterns.
|
|
579
|
+
|
|
580
|
+
## Deep Dive: Design System
|
|
581
|
+
|
|
582
|
+
Read `references/design-system.md` for grid layout patterns, the
|
|
583
|
+
critical Tailwind purge warning, component CSS discipline rules,
|
|
584
|
+
chrome-specific override scopes, and token creation workflow.
|
|
585
|
+
|
|
586
|
+
## Deep Dive: Repository Structure
|
|
587
|
+
|
|
588
|
+
Read `references/repo-structure.md` for the full annotated directory
|
|
589
|
+
layout, framework-specific paths, naming conventions, and generated
|
|
590
|
+
files list.
|