@gradial/aci 0.1.52-rc.29 → 0.1.52-rc.34-e2e.1
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/CHANGELOG.md +73 -0
- package/README.md +4 -4
- package/dist/audience-data-models.d.ts +201 -372
- package/dist/audience-data-models.js +283 -120
- package/dist/cli/compile-registry.mjs +0 -2
- package/dist/collect/aci.js +1 -1
- package/dist/collect/aci.js.sha256 +1 -1
- package/dist/collect/gapbuffer.js +11 -6
- package/dist/internal/platform/collectionstore/errors.d.ts +11 -0
- package/dist/internal/platform/collectionstore/errors.js +20 -0
- package/dist/next/error-pages.d.ts +4 -0
- package/dist/next/error-pages.js +4 -0
- package/dist/next/release-context.d.ts +29 -0
- package/dist/next/release-context.js +55 -0
- package/dist/next/server.d.ts +8 -0
- package/dist/next/server.js +14 -1
- package/dist/react/VideoPlayer.d.ts +1 -1
- package/dist/react/VideoPlayer.js +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +2 -3
- package/dist/testing/conformance/preset.js +3 -0
- package/dist/testing/conformance/root-identity.js +7 -26
- package/dist/testing/conformance/types.d.ts +3 -0
- package/dist/types/image.d.ts +54 -17
- package/dist/types/image.js +102 -18
- package/dist/types/media.d.ts +14 -6
- package/dist/types/video.d.ts +7 -3
- package/migrations/conformance-registry.md +22 -0
- package/package.json +8 -7
- package/src/cli/compile-registry.mjs +0 -2
- package/src/types/image.ts +133 -29
- package/src/types/video.ts +1 -1
- package/templates/astro/starter-content/src/fixtures/sections.ts +0 -1
- package/templates/common/.agents/skills/aci/SKILL.md +4 -3
- package/templates/common/.agents/skills/dam-delivery/SKILL.md +182 -193
- package/templates/common/.agents/skills/dam-delivery/references/permalink-grammar.md +137 -101
- package/templates/common/starter-content/.content/dam/README.md +74 -0
- package/templates/common/starter-content/.content/dam/folders/seed/000000000000000000000000101/governance.json +2 -1
- package/templates/common/starter-content/.content/dam/folders/seed/000000000000000000000000102/governance.json +2 -1
- package/templates/common/starter-content/.content/patterns/components/button.json +2 -2
- package/templates/common/starter-content/.content/patterns/components/container.json +2 -2
- package/templates/common/starter-content/.content/patterns/components/feature_card.json +2 -2
- package/templates/common/starter-content/.content/patterns/components/feature_grid.json +2 -2
- package/templates/common/starter-content/.content/patterns/components/home_hero.json +16 -7
- package/templates/common/starter-content/.content/patterns/components/personalization_choice.json +2 -2
- package/templates/common/starter-content/.content/patterns/components/site-footer.json +2 -2
- package/templates/common/starter-content/.content/patterns/components/site-navigation.json +2 -2
- package/templates/common/starter-content/.content/patterns/components/stats_bar.json +2 -2
- package/templates/nextjs/template/src/app/fonts/inter/OFL.txt +93 -0
- package/templates/nextjs/template/src/app/fonts/inter/inter-latin.woff2 +0 -0
- package/templates/nextjs/template/src/app/fonts/space-grotesk/OFL.txt +93 -0
- package/templates/nextjs/template/src/app/fonts/space-grotesk/space-grotesk-latin.woff2 +0 -0
- package/templates/nextjs/template/src/app/layout.tsx +10 -7
- package/templates/nextjs/template/tests/conformance.test.ts.tmpl +1 -0
- package/templates/common/.agents/skills/dam-delivery/references/parameter-contract.md +0 -107
package/dist/types/image.d.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export interface ImageSource {
|
|
3
|
-
/**
|
|
4
|
-
src
|
|
5
|
-
/**
|
|
3
|
+
/** Authored asset path — static sources only; DAM sources never set it. */
|
|
4
|
+
src?: string;
|
|
5
|
+
/** Declared candidate widths for the srcset (DAM sources). Empty or absent
|
|
6
|
+
* when the slot declares no outputs: the SDK renders the bare permalink
|
|
7
|
+
* (no params, original bytes). */
|
|
8
|
+
widths?: number[];
|
|
9
|
+
/** Layout width in pixels — the largest declared width, or the intrinsic
|
|
10
|
+
* width for static/original-aspect sources. */
|
|
6
11
|
width?: number;
|
|
7
|
-
/**
|
|
12
|
+
/** Layout height in pixels — the height at `width` (aspect carrier). */
|
|
8
13
|
height?: number;
|
|
9
|
-
/**
|
|
10
|
-
|
|
14
|
+
/** Transform fit for DAM sources: 'max' fits inside the box; 'crop' fills
|
|
15
|
+
* the declared aspect and emits an h param per srcset width. */
|
|
16
|
+
fit?: 'max' | 'crop';
|
|
11
17
|
/** sizes attribute from the slot contract. */
|
|
12
18
|
sizes?: string;
|
|
13
19
|
/** Media query scoping this source (art direction). */
|
|
@@ -20,8 +26,9 @@ export interface Image {
|
|
|
20
26
|
$type: 'gradial.image';
|
|
21
27
|
/** DAM asset identifier (or "static:<path>" for non-DAM assets). */
|
|
22
28
|
assetId: string;
|
|
23
|
-
/** DAM version identifier
|
|
24
|
-
|
|
29
|
+
/** DAM version identifier — absent for DAM images, which compile to the
|
|
30
|
+
* versionless live URL /dam/{assetId}; never set for static images. */
|
|
31
|
+
versionId?: string;
|
|
25
32
|
/** Accessible description of the image content. */
|
|
26
33
|
alt: string;
|
|
27
34
|
/** <source> candidates produced for the asset slot. */
|
|
@@ -38,10 +45,14 @@ export interface SlotOutput {
|
|
|
38
45
|
media?: string;
|
|
39
46
|
}
|
|
40
47
|
export declare const ImageSourceSchema: z.ZodObject<{
|
|
41
|
-
src: z.ZodString
|
|
42
|
-
|
|
48
|
+
src: z.ZodOptional<z.ZodString>;
|
|
49
|
+
widths: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
43
50
|
width: z.ZodOptional<z.ZodNumber>;
|
|
44
51
|
height: z.ZodOptional<z.ZodNumber>;
|
|
52
|
+
fit: z.ZodOptional<z.ZodEnum<{
|
|
53
|
+
max: "max";
|
|
54
|
+
crop: "crop";
|
|
55
|
+
}>>;
|
|
45
56
|
sizes: z.ZodOptional<z.ZodString>;
|
|
46
57
|
media: z.ZodOptional<z.ZodString>;
|
|
47
58
|
type: z.ZodString;
|
|
@@ -49,19 +60,43 @@ export declare const ImageSourceSchema: z.ZodObject<{
|
|
|
49
60
|
export declare const ImageSchema: z.ZodObject<{
|
|
50
61
|
$type: z.ZodLiteral<"gradial.image">;
|
|
51
62
|
assetId: z.ZodString;
|
|
52
|
-
versionId: z.ZodString
|
|
63
|
+
versionId: z.ZodOptional<z.ZodString>;
|
|
53
64
|
alt: z.ZodString;
|
|
54
65
|
sources: z.ZodArray<z.ZodObject<{
|
|
55
|
-
src: z.ZodString
|
|
56
|
-
|
|
66
|
+
src: z.ZodOptional<z.ZodString>;
|
|
67
|
+
widths: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
57
68
|
width: z.ZodOptional<z.ZodNumber>;
|
|
58
69
|
height: z.ZodOptional<z.ZodNumber>;
|
|
70
|
+
fit: z.ZodOptional<z.ZodEnum<{
|
|
71
|
+
max: "max";
|
|
72
|
+
crop: "crop";
|
|
73
|
+
}>>;
|
|
59
74
|
sizes: z.ZodOptional<z.ZodString>;
|
|
60
75
|
media: z.ZodOptional<z.ZodString>;
|
|
61
76
|
type: z.ZodString;
|
|
62
77
|
}, z.core.$strip>>;
|
|
63
78
|
}, z.core.$strip>;
|
|
79
|
+
/** Default DAM delivery quality, matching the edge's DEFAULT_QUALITY. */
|
|
80
|
+
export declare const DAM_IMAGE_QUALITY = 82;
|
|
81
|
+
/**
|
|
82
|
+
* Configures the origin for DAM permalinks. The default is the empty string:
|
|
83
|
+
* relative same-origin URLs (`/dam/{assetId}`), served by the site's own
|
|
84
|
+
* hosting edge. Set a base (e.g. `https://assets.example.com`) when images
|
|
85
|
+
* are consumed off-origin — email, external CMS fields, social cards — or
|
|
86
|
+
* when delivery is hosted on a dedicated domain. Call once during app setup,
|
|
87
|
+
* before the first render.
|
|
88
|
+
*/
|
|
89
|
+
export declare function configureDamAssets(options?: {
|
|
90
|
+
baseUrl?: string;
|
|
91
|
+
}): void;
|
|
92
|
+
/**
|
|
93
|
+
* The DAM permalink for an asset: `{base}/dam/{assetId}`. No params means
|
|
94
|
+
* original bytes.
|
|
95
|
+
*/
|
|
96
|
+
export declare function damAssetUrl(assetId: string): string;
|
|
64
97
|
export type ImageAttrs = Record<string, unknown>;
|
|
98
|
+
/** True when the image resolves to authored static bytes, not a DAM asset. */
|
|
99
|
+
export declare function isStaticImage(image: Image): boolean;
|
|
65
100
|
/**
|
|
66
101
|
* The primary rendition of an image — its first source entry.
|
|
67
102
|
*/
|
|
@@ -70,9 +105,11 @@ export declare function primaryImageSource(image: Image): ImageSource;
|
|
|
70
105
|
* Computes the full set of <img> attributes for an Image.
|
|
71
106
|
*
|
|
72
107
|
* Framework components (React <Picture>, Astro <img>, Svelte <img>) call this
|
|
73
|
-
* and spread the result into
|
|
74
|
-
* serialization.
|
|
75
|
-
*
|
|
76
|
-
*
|
|
108
|
+
* and spread the result into the native element. No HTML string
|
|
109
|
+
* serialization. DAM images construct their /dam/{assetId} permalink and
|
|
110
|
+
* srcset here from the slot-declared widths; static images pass their
|
|
111
|
+
* authored src through. Media-scoped sources need a <picture> wrapper and
|
|
112
|
+
* are not representable as plain <img> attributes, so srcset/sizes are taken
|
|
113
|
+
* from the first unscoped source only.
|
|
77
114
|
*/
|
|
78
115
|
export declare function imageAttrs(image: Image, overrides?: ImageAttrs): ImageAttrs;
|
package/dist/types/image.js
CHANGED
|
@@ -3,10 +3,11 @@ import { z } from 'zod';
|
|
|
3
3
|
// Zod schema
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
5
|
export const ImageSourceSchema = z.object({
|
|
6
|
-
src: z.string().min(1),
|
|
7
|
-
|
|
6
|
+
src: z.string().min(1).optional(),
|
|
7
|
+
widths: z.array(z.number().int().positive()).optional(),
|
|
8
8
|
width: z.number().int().nonnegative().optional(),
|
|
9
9
|
height: z.number().int().nonnegative().optional(),
|
|
10
|
+
fit: z.enum(['max', 'crop']).optional(),
|
|
10
11
|
sizes: z.string().min(1).optional(),
|
|
11
12
|
media: z.string().min(1).optional(),
|
|
12
13
|
type: z.string().min(1),
|
|
@@ -14,10 +15,97 @@ export const ImageSourceSchema = z.object({
|
|
|
14
15
|
export const ImageSchema = z.object({
|
|
15
16
|
$type: z.literal('gradial.image'),
|
|
16
17
|
assetId: z.string().min(1),
|
|
17
|
-
versionId: z.string().min(1),
|
|
18
|
+
versionId: z.string().min(1).optional(),
|
|
18
19
|
alt: z.string(),
|
|
19
20
|
sources: z.array(ImageSourceSchema).min(1),
|
|
20
21
|
});
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// DAM permalink construction (ADR 0062)
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/** Default DAM delivery quality, matching the edge's DEFAULT_QUALITY. */
|
|
26
|
+
export const DAM_IMAGE_QUALITY = 82;
|
|
27
|
+
let damAssetBase = '';
|
|
28
|
+
/**
|
|
29
|
+
* Configures the origin for DAM permalinks. The default is the empty string:
|
|
30
|
+
* relative same-origin URLs (`/dam/{assetId}`), served by the site's own
|
|
31
|
+
* hosting edge. Set a base (e.g. `https://assets.example.com`) when images
|
|
32
|
+
* are consumed off-origin — email, external CMS fields, social cards — or
|
|
33
|
+
* when delivery is hosted on a dedicated domain. Call once during app setup,
|
|
34
|
+
* before the first render.
|
|
35
|
+
*/
|
|
36
|
+
export function configureDamAssets(options = {}) {
|
|
37
|
+
damAssetBase = (options.baseUrl ?? '').replace(/\/+$/, '');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The DAM permalink for an asset: `{base}/dam/{assetId}`. No params means
|
|
41
|
+
* original bytes.
|
|
42
|
+
*/
|
|
43
|
+
export function damAssetUrl(assetId) {
|
|
44
|
+
return `${damAssetBase}/dam/${assetId}`;
|
|
45
|
+
}
|
|
46
|
+
const FORMAT_PARAMS = {
|
|
47
|
+
'image/avif': 'avif',
|
|
48
|
+
'image/webp': 'webp',
|
|
49
|
+
'image/jpeg': 'jpg',
|
|
50
|
+
'image/png': 'png',
|
|
51
|
+
};
|
|
52
|
+
function sourceFormatParams(source) {
|
|
53
|
+
const fm = FORMAT_PARAMS[source.type] ?? 'webp';
|
|
54
|
+
return `fm=${fm}&q=${DAM_IMAGE_QUALITY}`;
|
|
55
|
+
}
|
|
56
|
+
function srcsetWidth(assetId, source, width) {
|
|
57
|
+
const params = [`w=${width}`];
|
|
58
|
+
if (source.fit === 'crop' && source.width && source.height) {
|
|
59
|
+
const height = Math.round((width * source.height) / source.width);
|
|
60
|
+
params.push(`h=${height}`, 'fit=crop');
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
params.push('fit=max');
|
|
64
|
+
}
|
|
65
|
+
params.push(sourceFormatParams(source));
|
|
66
|
+
return `${damAssetUrl(assetId)}?${params.join('&')} ${width}w`;
|
|
67
|
+
}
|
|
68
|
+
function damImageAttrs(image, primary) {
|
|
69
|
+
const base = {
|
|
70
|
+
src: damAssetUrl(image.assetId),
|
|
71
|
+
alt: image.alt,
|
|
72
|
+
width: primary.width && primary.width > 0 ? primary.width : undefined,
|
|
73
|
+
height: primary.height && primary.height > 0 ? primary.height : undefined,
|
|
74
|
+
loading: 'lazy',
|
|
75
|
+
decoding: 'async',
|
|
76
|
+
};
|
|
77
|
+
if (primary.widths && primary.widths.length > 0) {
|
|
78
|
+
// Primary src at the largest declared width; srcset covers them all.
|
|
79
|
+
const largest = primary.widths[primary.widths.length - 1];
|
|
80
|
+
const params = [`w=${largest}`];
|
|
81
|
+
if (primary.fit === 'crop' && primary.width && primary.height) {
|
|
82
|
+
params.push(`h=${Math.round((largest * primary.height) / primary.width)}`, 'fit=crop');
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
params.push('fit=max');
|
|
86
|
+
}
|
|
87
|
+
params.push(sourceFormatParams(primary));
|
|
88
|
+
base.src = `${damAssetUrl(image.assetId)}?${params.join('&')}`;
|
|
89
|
+
if (primary.widths.length > 1) {
|
|
90
|
+
base.srcSet = primary.widths.map((w) => srcsetWidth(image.assetId, primary, w)).join(', ');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return base;
|
|
94
|
+
}
|
|
95
|
+
function staticImageAttrs(image, primary) {
|
|
96
|
+
return {
|
|
97
|
+
src: primary.src ?? '',
|
|
98
|
+
alt: image.alt,
|
|
99
|
+
width: primary.width && primary.width > 0 ? primary.width : undefined,
|
|
100
|
+
height: primary.height && primary.height > 0 ? primary.height : undefined,
|
|
101
|
+
loading: 'lazy',
|
|
102
|
+
decoding: 'async',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** True when the image resolves to authored static bytes, not a DAM asset. */
|
|
106
|
+
export function isStaticImage(image) {
|
|
107
|
+
return image.assetId.startsWith('static:');
|
|
108
|
+
}
|
|
21
109
|
/**
|
|
22
110
|
* The primary rendition of an image — its first source entry.
|
|
23
111
|
*/
|
|
@@ -28,23 +116,19 @@ export function primaryImageSource(image) {
|
|
|
28
116
|
* Computes the full set of <img> attributes for an Image.
|
|
29
117
|
*
|
|
30
118
|
* Framework components (React <Picture>, Astro <img>, Svelte <img>) call this
|
|
31
|
-
* and spread the result into
|
|
32
|
-
* serialization.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
119
|
+
* and spread the result into the native element. No HTML string
|
|
120
|
+
* serialization. DAM images construct their /dam/{assetId} permalink and
|
|
121
|
+
* srcset here from the slot-declared widths; static images pass their
|
|
122
|
+
* authored src through. Media-scoped sources need a <picture> wrapper and
|
|
123
|
+
* are not representable as plain <img> attributes, so srcset/sizes are taken
|
|
124
|
+
* from the first unscoped source only.
|
|
35
125
|
*/
|
|
36
126
|
export function imageAttrs(image, overrides = {}) {
|
|
37
127
|
const primary = image.sources[0];
|
|
38
128
|
const unscoped = image.sources.find((s) => !s.media) ?? primary;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
srcSet: unscoped?.srcset || undefined,
|
|
45
|
-
sizes: unscoped?.sizes || undefined,
|
|
46
|
-
loading: 'lazy',
|
|
47
|
-
decoding: 'async',
|
|
48
|
-
...overrides,
|
|
49
|
-
};
|
|
129
|
+
const base = isStaticImage(image) ? staticImageAttrs(image, primary) : damImageAttrs(image, primary);
|
|
130
|
+
if (unscoped?.sizes) {
|
|
131
|
+
base.sizes = unscoped.sizes;
|
|
132
|
+
}
|
|
133
|
+
return { ...base, ...overrides };
|
|
50
134
|
}
|
package/dist/types/media.d.ts
CHANGED
|
@@ -7,13 +7,17 @@ export type Asset = Image | Video;
|
|
|
7
7
|
export declare const AssetSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
8
8
|
$type: z.ZodLiteral<"gradial.image">;
|
|
9
9
|
assetId: z.ZodString;
|
|
10
|
-
versionId: z.ZodString
|
|
10
|
+
versionId: z.ZodOptional<z.ZodString>;
|
|
11
11
|
alt: z.ZodString;
|
|
12
12
|
sources: z.ZodArray<z.ZodObject<{
|
|
13
|
-
src: z.ZodString
|
|
14
|
-
|
|
13
|
+
src: z.ZodOptional<z.ZodString>;
|
|
14
|
+
widths: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
15
15
|
width: z.ZodOptional<z.ZodNumber>;
|
|
16
16
|
height: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
fit: z.ZodOptional<z.ZodEnum<{
|
|
18
|
+
max: "max";
|
|
19
|
+
crop: "crop";
|
|
20
|
+
}>>;
|
|
17
21
|
sizes: z.ZodOptional<z.ZodString>;
|
|
18
22
|
media: z.ZodOptional<z.ZodString>;
|
|
19
23
|
type: z.ZodString;
|
|
@@ -30,13 +34,17 @@ export declare const AssetSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
30
34
|
poster: z.ZodOptional<z.ZodObject<{
|
|
31
35
|
$type: z.ZodLiteral<"gradial.image">;
|
|
32
36
|
assetId: z.ZodString;
|
|
33
|
-
versionId: z.ZodString
|
|
37
|
+
versionId: z.ZodOptional<z.ZodString>;
|
|
34
38
|
alt: z.ZodString;
|
|
35
39
|
sources: z.ZodArray<z.ZodObject<{
|
|
36
|
-
src: z.ZodString
|
|
37
|
-
|
|
40
|
+
src: z.ZodOptional<z.ZodString>;
|
|
41
|
+
widths: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
38
42
|
width: z.ZodOptional<z.ZodNumber>;
|
|
39
43
|
height: z.ZodOptional<z.ZodNumber>;
|
|
44
|
+
fit: z.ZodOptional<z.ZodEnum<{
|
|
45
|
+
max: "max";
|
|
46
|
+
crop: "crop";
|
|
47
|
+
}>>;
|
|
40
48
|
sizes: z.ZodOptional<z.ZodString>;
|
|
41
49
|
media: z.ZodOptional<z.ZodString>;
|
|
42
50
|
type: z.ZodString;
|
package/dist/types/video.d.ts
CHANGED
|
@@ -50,13 +50,17 @@ export declare const VideoSchema: z.ZodObject<{
|
|
|
50
50
|
poster: z.ZodOptional<z.ZodObject<{
|
|
51
51
|
$type: z.ZodLiteral<"gradial.image">;
|
|
52
52
|
assetId: z.ZodString;
|
|
53
|
-
versionId: z.ZodString
|
|
53
|
+
versionId: z.ZodOptional<z.ZodString>;
|
|
54
54
|
alt: z.ZodString;
|
|
55
55
|
sources: z.ZodArray<z.ZodObject<{
|
|
56
|
-
src: z.ZodString
|
|
57
|
-
|
|
56
|
+
src: z.ZodOptional<z.ZodString>;
|
|
57
|
+
widths: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
58
58
|
width: z.ZodOptional<z.ZodNumber>;
|
|
59
59
|
height: z.ZodOptional<z.ZodNumber>;
|
|
60
|
+
fit: z.ZodOptional<z.ZodEnum<{
|
|
61
|
+
max: "max";
|
|
62
|
+
crop: "crop";
|
|
63
|
+
}>>;
|
|
60
64
|
sizes: z.ZodOptional<z.ZodString>;
|
|
61
65
|
media: z.ZodOptional<z.ZodString>;
|
|
62
66
|
type: z.ZodString;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Designate the render registry for Next conformance
|
|
2
|
+
|
|
3
|
+
Set `registry` in the site's conformance options to the module its page entry
|
|
4
|
+
uses to create the ACI page:
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { conformancePreset } from '@gradial/aci/testing';
|
|
8
|
+
|
|
9
|
+
conformancePreset({
|
|
10
|
+
framework: 'next',
|
|
11
|
+
registry: 'src/cms/registry.ts',
|
|
12
|
+
});
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Paths resolve against `rootDir`, which defaults to the test process's current
|
|
16
|
+
working directory. Absolute paths also work. Use the app's full render registry,
|
|
17
|
+
including every registered component. The named module must export the registry.
|
|
18
|
+
|
|
19
|
+
Run the site's `npm test`. The `rootIdentity` check renders components from the
|
|
20
|
+
named module and fails if a rendered root omits its ACI identity attributes.
|
|
21
|
+
Missing configuration or a missing module fails before importing site code.
|
|
22
|
+
Astro's existing authored-block scan does not require this option.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradial/aci",
|
|
3
|
-
"version": "0.1.52-rc.
|
|
3
|
+
"version": "0.1.52-rc.34-e2e.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/gradial/bare-metal-go.git",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"templates/nextjs",
|
|
21
21
|
"templates/astro",
|
|
22
22
|
"CHANGELOG.md",
|
|
23
|
+
"migrations",
|
|
23
24
|
"README.md"
|
|
24
25
|
],
|
|
25
26
|
"bin": {
|
|
@@ -159,11 +160,11 @@
|
|
|
159
160
|
"zod": "^4.0.0"
|
|
160
161
|
},
|
|
161
162
|
"optionalDependencies": {
|
|
162
|
-
"@gradial/aci-cli-darwin-arm64": "0.1.52-rc.
|
|
163
|
-
"@gradial/aci-cli-darwin-x64": "0.1.52-rc.
|
|
164
|
-
"@gradial/aci-cli-linux-arm64": "0.1.52-rc.
|
|
165
|
-
"@gradial/aci-cli-linux-x64": "0.1.52-rc.
|
|
166
|
-
"@gradial/aci-cli-win32-arm64": "0.1.52-rc.
|
|
167
|
-
"@gradial/aci-cli-win32-x64": "0.1.52-rc.
|
|
163
|
+
"@gradial/aci-cli-darwin-arm64": "0.1.52-rc.34-e2e.1",
|
|
164
|
+
"@gradial/aci-cli-darwin-x64": "0.1.52-rc.34-e2e.1",
|
|
165
|
+
"@gradial/aci-cli-linux-arm64": "0.1.52-rc.34-e2e.1",
|
|
166
|
+
"@gradial/aci-cli-linux-x64": "0.1.52-rc.34-e2e.1",
|
|
167
|
+
"@gradial/aci-cli-win32-arm64": "0.1.52-rc.34-e2e.1",
|
|
168
|
+
"@gradial/aci-cli-win32-x64": "0.1.52-rc.34-e2e.1"
|
|
168
169
|
}
|
|
169
170
|
}
|
|
@@ -346,12 +346,10 @@ function isImageSchema(schema) {
|
|
|
346
346
|
return compiled?.type === 'object'
|
|
347
347
|
&& properties.$type?.const === 'gradial.image'
|
|
348
348
|
&& properties.assetId?.type === 'string'
|
|
349
|
-
&& properties.versionId?.type === 'string'
|
|
350
349
|
&& properties.alt?.type === 'string'
|
|
351
350
|
&& properties.sources?.type === 'array'
|
|
352
351
|
&& required.has('$type')
|
|
353
352
|
&& required.has('assetId')
|
|
354
|
-
&& required.has('versionId')
|
|
355
353
|
&& required.has('alt')
|
|
356
354
|
&& required.has('sources');
|
|
357
355
|
} catch {
|
package/src/types/image.ts
CHANGED
|
@@ -3,19 +3,28 @@ import { z } from 'zod';
|
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
// Image source variants
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
6
|
+
// A DAM source carries the asset identity's slot presentation — the declared
|
|
7
|
+
// candidate widths, the fit, and the MIME format — and NEVER a URL: the SDK
|
|
8
|
+
// constructs the /dam/{assetId} permalink and its srcset at render time
|
|
9
|
+
// (ADR 0062: the compiler emits asset ids only). Static assets keep their
|
|
10
|
+
// authored src passthrough.
|
|
8
11
|
// ---------------------------------------------------------------------------
|
|
9
12
|
|
|
10
13
|
export interface ImageSource {
|
|
11
|
-
/**
|
|
12
|
-
src
|
|
13
|
-
/**
|
|
14
|
+
/** Authored asset path — static sources only; DAM sources never set it. */
|
|
15
|
+
src?: string;
|
|
16
|
+
/** Declared candidate widths for the srcset (DAM sources). Empty or absent
|
|
17
|
+
* when the slot declares no outputs: the SDK renders the bare permalink
|
|
18
|
+
* (no params, original bytes). */
|
|
19
|
+
widths?: number[];
|
|
20
|
+
/** Layout width in pixels — the largest declared width, or the intrinsic
|
|
21
|
+
* width for static/original-aspect sources. */
|
|
14
22
|
width?: number;
|
|
15
|
-
/**
|
|
23
|
+
/** Layout height in pixels — the height at `width` (aspect carrier). */
|
|
16
24
|
height?: number;
|
|
17
|
-
/**
|
|
18
|
-
srcset
|
|
25
|
+
/** Transform fit for DAM sources: 'max' fits inside the box; 'crop' fills
|
|
26
|
+
* the declared aspect and emits an h param per srcset width. */
|
|
27
|
+
fit?: 'max' | 'crop';
|
|
19
28
|
/** sizes attribute from the slot contract. */
|
|
20
29
|
sizes?: string;
|
|
21
30
|
/** Media query scoping this source (art direction). */
|
|
@@ -25,7 +34,7 @@ export interface ImageSource {
|
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
// ---------------------------------------------------------------------------
|
|
28
|
-
// Image — DAM-backed image asset with compiler-resolved
|
|
37
|
+
// Image — DAM-backed image asset with compiler-resolved live asset URLs
|
|
29
38
|
//
|
|
30
39
|
// Content authors write an asset reference: { "$type": "dam.assetRef", ... }
|
|
31
40
|
// or for static files: { "$type": "static.assetRef", "src": "/path" }
|
|
@@ -37,8 +46,9 @@ export interface Image {
|
|
|
37
46
|
$type: 'gradial.image';
|
|
38
47
|
/** DAM asset identifier (or "static:<path>" for non-DAM assets). */
|
|
39
48
|
assetId: string;
|
|
40
|
-
/** DAM version identifier
|
|
41
|
-
|
|
49
|
+
/** DAM version identifier — absent for DAM images, which compile to the
|
|
50
|
+
* versionless live URL /dam/{assetId}; never set for static images. */
|
|
51
|
+
versionId?: string;
|
|
42
52
|
/** Accessible description of the image content. */
|
|
43
53
|
alt: string;
|
|
44
54
|
/** <source> candidates produced for the asset slot. */
|
|
@@ -66,10 +76,11 @@ export interface SlotOutput {
|
|
|
66
76
|
// ---------------------------------------------------------------------------
|
|
67
77
|
|
|
68
78
|
export const ImageSourceSchema = z.object({
|
|
69
|
-
src: z.string().min(1),
|
|
70
|
-
|
|
79
|
+
src: z.string().min(1).optional(),
|
|
80
|
+
widths: z.array(z.number().int().positive()).optional(),
|
|
71
81
|
width: z.number().int().nonnegative().optional(),
|
|
72
82
|
height: z.number().int().nonnegative().optional(),
|
|
83
|
+
fit: z.enum(['max', 'crop']).optional(),
|
|
73
84
|
sizes: z.string().min(1).optional(),
|
|
74
85
|
media: z.string().min(1).optional(),
|
|
75
86
|
type: z.string().min(1),
|
|
@@ -78,17 +89,113 @@ export const ImageSourceSchema = z.object({
|
|
|
78
89
|
export const ImageSchema = z.object({
|
|
79
90
|
$type: z.literal('gradial.image'),
|
|
80
91
|
assetId: z.string().min(1),
|
|
81
|
-
versionId: z.string().min(1),
|
|
92
|
+
versionId: z.string().min(1).optional(),
|
|
82
93
|
alt: z.string(),
|
|
83
94
|
sources: z.array(ImageSourceSchema).min(1),
|
|
84
95
|
});
|
|
85
96
|
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// DAM permalink construction (ADR 0062)
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
/** Default DAM delivery quality, matching the edge's DEFAULT_QUALITY. */
|
|
102
|
+
export const DAM_IMAGE_QUALITY = 82;
|
|
103
|
+
|
|
104
|
+
let damAssetBase = '';
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Configures the origin for DAM permalinks. The default is the empty string:
|
|
108
|
+
* relative same-origin URLs (`/dam/{assetId}`), served by the site's own
|
|
109
|
+
* hosting edge. Set a base (e.g. `https://assets.example.com`) when images
|
|
110
|
+
* are consumed off-origin — email, external CMS fields, social cards — or
|
|
111
|
+
* when delivery is hosted on a dedicated domain. Call once during app setup,
|
|
112
|
+
* before the first render.
|
|
113
|
+
*/
|
|
114
|
+
export function configureDamAssets(options: { baseUrl?: string } = {}): void {
|
|
115
|
+
damAssetBase = (options.baseUrl ?? '').replace(/\/+$/, '');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The DAM permalink for an asset: `{base}/dam/{assetId}`. No params means
|
|
120
|
+
* original bytes.
|
|
121
|
+
*/
|
|
122
|
+
export function damAssetUrl(assetId: string): string {
|
|
123
|
+
return `${damAssetBase}/dam/${assetId}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const FORMAT_PARAMS: Record<string, string> = {
|
|
127
|
+
'image/avif': 'avif',
|
|
128
|
+
'image/webp': 'webp',
|
|
129
|
+
'image/jpeg': 'jpg',
|
|
130
|
+
'image/png': 'png',
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
function sourceFormatParams(source: ImageSource): string {
|
|
134
|
+
const fm = FORMAT_PARAMS[source.type] ?? 'webp';
|
|
135
|
+
return `fm=${fm}&q=${DAM_IMAGE_QUALITY}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function srcsetWidth(assetId: string, source: ImageSource, width: number): string {
|
|
139
|
+
const params = [`w=${width}`];
|
|
140
|
+
if (source.fit === 'crop' && source.width && source.height) {
|
|
141
|
+
const height = Math.round((width * source.height) / source.width);
|
|
142
|
+
params.push(`h=${height}`, 'fit=crop');
|
|
143
|
+
} else {
|
|
144
|
+
params.push('fit=max');
|
|
145
|
+
}
|
|
146
|
+
params.push(sourceFormatParams(source));
|
|
147
|
+
return `${damAssetUrl(assetId)}?${params.join('&')} ${width}w`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function damImageAttrs(image: Image, primary: ImageSource): ImageAttrs {
|
|
151
|
+
const base: ImageAttrs = {
|
|
152
|
+
src: damAssetUrl(image.assetId),
|
|
153
|
+
alt: image.alt,
|
|
154
|
+
width: primary.width && primary.width > 0 ? primary.width : undefined,
|
|
155
|
+
height: primary.height && primary.height > 0 ? primary.height : undefined,
|
|
156
|
+
loading: 'lazy',
|
|
157
|
+
decoding: 'async',
|
|
158
|
+
};
|
|
159
|
+
if (primary.widths && primary.widths.length > 0) {
|
|
160
|
+
// Primary src at the largest declared width; srcset covers them all.
|
|
161
|
+
const largest = primary.widths[primary.widths.length - 1];
|
|
162
|
+
const params = [`w=${largest}`];
|
|
163
|
+
if (primary.fit === 'crop' && primary.width && primary.height) {
|
|
164
|
+
params.push(`h=${Math.round((largest * primary.height) / primary.width)}`, 'fit=crop');
|
|
165
|
+
} else {
|
|
166
|
+
params.push('fit=max');
|
|
167
|
+
}
|
|
168
|
+
params.push(sourceFormatParams(primary));
|
|
169
|
+
base.src = `${damAssetUrl(image.assetId)}?${params.join('&')}`;
|
|
170
|
+
if (primary.widths.length > 1) {
|
|
171
|
+
base.srcSet = primary.widths.map((w) => srcsetWidth(image.assetId, primary, w)).join(', ');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return base;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function staticImageAttrs(image: Image, primary: ImageSource): ImageAttrs {
|
|
178
|
+
return {
|
|
179
|
+
src: primary.src ?? '',
|
|
180
|
+
alt: image.alt,
|
|
181
|
+
width: primary.width && primary.width > 0 ? primary.width : undefined,
|
|
182
|
+
height: primary.height && primary.height > 0 ? primary.height : undefined,
|
|
183
|
+
loading: 'lazy',
|
|
184
|
+
decoding: 'async',
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
86
188
|
// ---------------------------------------------------------------------------
|
|
87
189
|
// Shared attrs helper — consumed by React, Astro, and Svelte components
|
|
88
190
|
// ---------------------------------------------------------------------------
|
|
89
191
|
|
|
90
192
|
export type ImageAttrs = Record<string, unknown>;
|
|
91
193
|
|
|
194
|
+
/** True when the image resolves to authored static bytes, not a DAM asset. */
|
|
195
|
+
export function isStaticImage(image: Image): boolean {
|
|
196
|
+
return image.assetId.startsWith('static:');
|
|
197
|
+
}
|
|
198
|
+
|
|
92
199
|
/**
|
|
93
200
|
* The primary rendition of an image — its first source entry.
|
|
94
201
|
*/
|
|
@@ -100,24 +207,21 @@ export function primaryImageSource(image: Image): ImageSource {
|
|
|
100
207
|
* Computes the full set of <img> attributes for an Image.
|
|
101
208
|
*
|
|
102
209
|
* Framework components (React <Picture>, Astro <img>, Svelte <img>) call this
|
|
103
|
-
* and spread the result into
|
|
104
|
-
* serialization.
|
|
105
|
-
*
|
|
106
|
-
*
|
|
210
|
+
* and spread the result into the native element. No HTML string
|
|
211
|
+
* serialization. DAM images construct their /dam/{assetId} permalink and
|
|
212
|
+
* srcset here from the slot-declared widths; static images pass their
|
|
213
|
+
* authored src through. Media-scoped sources need a <picture> wrapper and
|
|
214
|
+
* are not representable as plain <img> attributes, so srcset/sizes are taken
|
|
215
|
+
* from the first unscoped source only.
|
|
107
216
|
*/
|
|
108
217
|
export function imageAttrs(image: Image, overrides: ImageAttrs = {}): ImageAttrs {
|
|
109
218
|
const primary = image.sources[0];
|
|
110
219
|
const unscoped = image.sources.find((s) => !s.media) ?? primary;
|
|
111
220
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
sizes: unscoped?.sizes || undefined,
|
|
119
|
-
loading: 'lazy',
|
|
120
|
-
decoding: 'async',
|
|
121
|
-
...overrides,
|
|
122
|
-
};
|
|
221
|
+
const base = isStaticImage(image) ? staticImageAttrs(image, primary) : damImageAttrs(image, primary);
|
|
222
|
+
|
|
223
|
+
if (unscoped?.sizes) {
|
|
224
|
+
base.sizes = unscoped.sizes;
|
|
225
|
+
}
|
|
226
|
+
return { ...base, ...overrides };
|
|
123
227
|
}
|
package/src/types/video.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface VideoSource {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
|
-
// Video — DAM-backed video asset with compiler-resolved
|
|
14
|
+
// Video — DAM-backed video asset with compiler-resolved live asset URLs
|
|
15
15
|
//
|
|
16
16
|
// Content authors write: { "$type": "dam.assetRef", ... }
|
|
17
17
|
// or for static files: { "$type": "static.assetRef", "src": "/path" }
|
|
@@ -26,7 +26,6 @@ export const sectionStoryFixtures: SectionStoryFixture[] = [
|
|
|
26
26
|
image: {
|
|
27
27
|
$type: 'gradial.image',
|
|
28
28
|
assetId: '3Ih26etWBTYfnAd0EjD0pP2h902',
|
|
29
|
-
versionId: 'v0001',
|
|
30
29
|
alt: 'Seattle skyline at dusk',
|
|
31
30
|
sources: [
|
|
32
31
|
{ src: '/assets/seattle.avif', width: 1920, height: 1280, sizes: '100vw', type: 'image/avif' }
|
|
@@ -262,6 +262,7 @@ site or design into ACI.
|
|
|
262
262
|
|
|
263
263
|
## Deep Dive: DAM Delivery
|
|
264
264
|
|
|
265
|
-
Read `.agents/skills/dam-delivery/SKILL.md` for
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
Read `.agents/skills/dam-delivery/SKILL.md` for public DAM asset links
|
|
266
|
+
(`/dam/{assetId}`): how to shape an image with the `w`, `h`, `fit`, `fm`,
|
|
267
|
+
`q` query, what makes a link serve or stop serving, and when to use a
|
|
268
|
+
link instead of a pre-baked derivative.
|