@gradial/aci 0.1.52-rc.4 → 0.1.52-rc.6

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 (41) hide show
  1. package/README.md +4 -4
  2. package/dist/cli/compile-registry.d.mts +5 -1
  3. package/dist/cli/compile-registry.mjs +25 -10
  4. package/dist/cli/validate-content.d.mts +31 -1
  5. package/dist/cli/validate-content.mjs +50 -12
  6. package/dist/content/provider.d.ts +0 -1
  7. package/dist/content/resolve-slots.d.ts +9 -2
  8. package/dist/content/resolve-slots.js +5 -3
  9. package/dist/content/types.d.ts +2 -0
  10. package/dist/next/error-seat-payload.js +1 -1
  11. package/dist/providers/s3.js +4 -11
  12. package/dist/types/block-ref.d.ts +2 -0
  13. package/dist/types/block-ref.js +1 -0
  14. package/package.json +7 -17
  15. package/src/cli/compile-registry.mjs +24 -10
  16. package/src/cli/validate-content.mjs +52 -13
  17. package/src/types/block-ref.test.ts +45 -0
  18. package/src/types/block-ref.ts +2 -0
  19. package/templates/astro/starter-content/src/fixtures/atomicBlocks.ts +7 -6
  20. package/templates/astro/starter-content/src/fixtures/composition.ts +3 -2
  21. package/templates/astro/starter-content/src/fixtures/sections.ts +2 -1
  22. package/templates/common/.agents/skills/authoring/SKILL.md +11 -3
  23. package/templates/common/.agents/skills/authoring/references/block-slots.md +3 -0
  24. package/templates/common/.content/pages/home/_index.json +10 -0
  25. package/templates/common/starter-content/.content/.rules-baseline.json +8 -0
  26. package/templates/common/starter-content/.content/content-rules/no-public-resource-paths.json +6 -0
  27. package/templates/common/starter-content/.content/fragments/navbar/_index.json +23 -20
  28. package/templates/common/starter-content/.content/fragments/site-footer/_index.json +59 -56
  29. package/templates/common/starter-content/.content/pages/404/_index.json +1 -0
  30. package/templates/common/starter-content/.content/pages/about/_index.json +3 -0
  31. package/templates/common/starter-content/.content/pages/error/_index.json +1 -0
  32. package/templates/common/starter-content/.content/pages/home/_index.json +9 -0
  33. package/templates/common/starter-content/.content/patterns/components/button.json +55 -0
  34. package/templates/common/starter-content/.content/patterns/components/container.json +62 -0
  35. package/templates/common/starter-content/.content/patterns/components/feature_card.json +30 -0
  36. package/templates/common/starter-content/.content/patterns/components/feature_grid.json +57 -0
  37. package/templates/common/starter-content/.content/patterns/components/home_hero.json +142 -0
  38. package/templates/common/starter-content/.content/patterns/components/site-footer.json +203 -0
  39. package/templates/common/starter-content/.content/patterns/components/site-navigation.json +115 -0
  40. package/templates/common/starter-content/.content/patterns/components/stats_bar.json +45 -0
  41. package/templates/common/starter-content/.content/patterns/layouts/default.json +14 -0
@@ -14,6 +14,7 @@ export const sectionStoryFixtures: SectionStoryFixture[] = [
14
14
  storybookId: 'sections-home-hero--default',
15
15
  title: 'Home Hero',
16
16
  block: {
17
+ $type: 'component',
17
18
  id: 'fixture-home-hero',
18
19
  component: 'home_hero',
19
20
  props: {
@@ -22,7 +23,7 @@ export const sectionStoryFixtures: SectionStoryFixture[] = [
22
23
  description: 'One content model, three renderers. Build once in Next.js, Astro, and SvelteKit—then prove parity with shared fixtures.',
23
24
  ctaLabel: 'Get started',
24
25
  ctaHref: '/docs',
25
- image: {
26
+ image: {
26
27
  $type: 'gradial.image',
27
28
  assetId: 'fixture_seattle',
28
29
  versionId: 'v0001',
@@ -98,6 +98,7 @@ The `home` slug is special-cased to route `/`. All other slugs map directly.
98
98
 
99
99
  ```json
100
100
  {
101
+ "$type": "component",
101
102
  "id": "hero-section",
102
103
  "component": "home_hero",
103
104
  "props": {
@@ -109,6 +110,7 @@ The `home` slug is special-cased to route `/`. All other slugs map directly.
109
110
  ```
110
111
 
111
112
  - `component` must match a contract `id` exactly.
113
+ - Every embedded block must declare `"$type": "component"`.
112
114
  - `props` must validate against the contract's Zod schema.
113
115
  - `id` is unique within the page.
114
116
 
@@ -141,12 +143,14 @@ export const containerContract = defineComponentContract({
141
143
  **Content JSON:**
142
144
  ```json
143
145
  {
146
+ "$type": "component",
144
147
  "id": "cta-section",
145
148
  "component": "container",
146
149
  "props": {
147
150
  "className": "space-y-4",
148
151
  "content": [
149
152
  {
153
+ "$type": "component",
150
154
  "id": "primary-cta",
151
155
  "component": "button",
152
156
  "props": {
@@ -156,6 +160,7 @@ export const containerContract = defineComponentContract({
156
160
  }
157
161
  },
158
162
  {
163
+ "$type": "component",
159
164
  "id": "secondary-cta",
160
165
  "component": "button",
161
166
  "props": {
@@ -182,6 +187,7 @@ The `button` component is a common atomic block:
182
187
 
183
188
  ```json
184
189
  {
190
+ "$type": "component",
185
191
  "id": "cta-button",
186
192
  "component": "button",
187
193
  "props": {
@@ -482,6 +488,7 @@ framework.
482
488
  ```json
483
489
  // .content/pages/home/_index.json (inside regions.main)
484
490
  {
491
+ "$type": "component",
485
492
  "id": "features",
486
493
  "component": "feature_showcase",
487
494
  "props": {
@@ -570,9 +577,10 @@ export default [
570
577
  ];
571
578
  ```
572
579
 
573
- Fragment content lives in `.content/fragments/{id}/_index.json` and has
574
- a `component` + `props` just like page blocks. Chrome components (Header,
575
- Footer) are registered in the registry alongside page components.
580
+ Fragment content lives in `.content/fragments/{id}/_index.json` and retains
581
+ the resource envelope `"$type": "fragment"` while declaring `component` +
582
+ `props`. Chrome components (Header, Footer) are registered in the registry
583
+ alongside page components.
576
584
 
577
585
  The `layout` field in page JSON is **required** — there is no implicit
578
586
  default. Every page must declare which layout it uses.
@@ -113,12 +113,14 @@ type RenderChildren<T extends readonly BlockRef[]> = (blocks: T) => Promise<Reac
113
113
 
114
114
  ```json
115
115
  {
116
+ "$type": "component",
116
117
  "id": "cta-section",
117
118
  "component": "container",
118
119
  "props": {
119
120
  "theme": "dark",
120
121
  "blocks": [
121
122
  {
123
+ "$type": "component",
122
124
  "id": "primary-cta",
123
125
  "component": "callout",
124
126
  "props": {
@@ -127,6 +129,7 @@ type RenderChildren<T extends readonly BlockRef[]> = (blocks: T) => Promise<Reac
127
129
  }
128
130
  },
129
131
  {
132
+ "$type": "component",
130
133
  "id": "heading",
131
134
  "component": "heading",
132
135
  "props": {
@@ -11,6 +11,7 @@
11
11
  "regions": {
12
12
  "main": [
13
13
  {
14
+ "$type": "component",
14
15
  "id": "hero",
15
16
  "component": "home_hero",
16
17
  "props": {
@@ -27,6 +28,7 @@
27
28
  }
28
29
  },
29
30
  {
31
+ "$type": "component",
30
32
  "id": "why-meridian",
31
33
  "component": "feature_grid",
32
34
  "props": {
@@ -34,6 +36,7 @@
34
36
  "subtitle": "Engineered for the edges of the world.",
35
37
  "features": [
36
38
  {
39
+ "$type": "component",
37
40
  "id": "global-coverage",
38
41
  "component": "feature_card",
39
42
  "props": {
@@ -43,6 +46,7 @@
43
46
  }
44
47
  },
45
48
  {
49
+ "$type": "component",
46
50
  "id": "low-latency",
47
51
  "component": "feature_card",
48
52
  "props": {
@@ -52,6 +56,7 @@
52
56
  }
53
57
  },
54
58
  {
59
+ "$type": "component",
55
60
  "id": "easy-setup",
56
61
  "component": "feature_card",
57
62
  "props": {
@@ -64,6 +69,7 @@
64
69
  }
65
70
  },
66
71
  {
72
+ "$type": "component",
67
73
  "id": "personalization-choice",
68
74
  "component": "personalization_choice",
69
75
  "props": {
@@ -75,6 +81,7 @@
75
81
  }
76
82
  },
77
83
  {
84
+ "$type": "component",
78
85
  "id": "stats",
79
86
  "component": "stats_bar",
80
87
  "props": {
@@ -87,6 +94,7 @@
87
94
  }
88
95
  },
89
96
  {
97
+ "$type": "component",
90
98
  "id": "cta",
91
99
  "component": "container",
92
100
  "props": {
@@ -94,6 +102,7 @@
94
102
  "className": "items-center justify-center text-center",
95
103
  "content": [
96
104
  {
105
+ "$type": "component",
97
106
  "id": "cta-primary",
98
107
  "component": "button",
99
108
  "props": {
@@ -105,6 +114,7 @@
105
114
  }
106
115
  },
107
116
  {
117
+ "$type": "component",
108
118
  "id": "cta-secondary",
109
119
  "component": "button",
110
120
  "props": {
@@ -0,0 +1,8 @@
1
+ {
2
+ "version": 1,
3
+ "rules": {
4
+ "no-public-resource-paths": [
5
+ "/assets/meridian-logo.svg"
6
+ ]
7
+ }
8
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "kind": "forbid-string-pattern",
3
+ "pattern": "^/[^\\s]+\\.(png|jpe?g|webp|gif|svg|avif|mp4|webm|ico)$",
4
+ "appliesTo": "all-string-props",
5
+ "message": "Content must not reference front-end public/ resources."
6
+ }
@@ -1,25 +1,28 @@
1
1
  {
2
2
  "$type": "fragment",
3
3
  "fragmentId": "navbar",
4
- "component": "site-navigation",
5
- "props": {
6
- "brandHref": "/",
7
- "brandLabel": "Meridian",
8
- "brandLogo": {
9
- "src": "/assets/meridian-logo.svg",
10
- "alt": "Meridian"
11
- },
12
- "navigation": [
13
- { "id": "home", "label": "Home", "href": "/" },
14
- { "id": "plans", "label": "Plans", "href": "/plans/" },
15
- { "id": "devices", "label": "Devices", "href": "/devices/" },
16
- { "id": "business", "label": "Business", "href": "/business/" },
17
- { "id": "blog", "label": "Blog", "href": "/blog/" },
18
- { "id": "about", "label": "About", "href": "/about/" }
19
- ],
20
- "utilities": [
21
- { "id": "sign-in", "label": "Sign in", "href": "/sign-in/", "variant": "ghost" },
22
- { "id": "contact-sales", "label": "Contact sales", "href": "/contact-sales/", "variant": "primary" }
23
- ]
4
+ "block": {
5
+ "$type": "component",
6
+ "component": "site-navigation",
7
+ "props": {
8
+ "brandHref": "/",
9
+ "brandLabel": "Meridian",
10
+ "brandLogo": {
11
+ "src": "/assets/meridian-logo.svg",
12
+ "alt": "Meridian"
13
+ },
14
+ "navigation": [
15
+ { "id": "home", "label": "Home", "href": "/" },
16
+ { "id": "plans", "label": "Plans", "href": "/plans/" },
17
+ { "id": "devices", "label": "Devices", "href": "/devices/" },
18
+ { "id": "business", "label": "Business", "href": "/business/" },
19
+ { "id": "blog", "label": "Blog", "href": "/blog/" },
20
+ { "id": "about", "label": "About", "href": "/about/" }
21
+ ],
22
+ "utilities": [
23
+ { "id": "sign-in", "label": "Sign in", "href": "/sign-in/", "variant": "ghost" },
24
+ { "id": "contact-sales", "label": "Contact sales", "href": "/contact-sales/", "variant": "primary" }
25
+ ]
26
+ }
24
27
  }
25
28
  }
@@ -1,62 +1,65 @@
1
1
  {
2
2
  "$type": "fragment",
3
3
  "fragmentId": "site-footer",
4
- "component": "site-footer",
5
- "props": {
6
- "brandHref": "/",
7
- "brandLabel": "Meridian",
8
- "brandLogo": {
9
- "src": "/assets/meridian-logo.svg",
10
- "alt": "Meridian"
11
- },
12
- "columns": [
13
- {
14
- "id": "products",
15
- "title": "Products",
16
- "links": [
17
- { "id": "plans", "label": "Plans", "href": "/plans/" },
18
- { "id": "devices", "label": "Devices", "href": "/devices/" },
19
- { "id": "coverage", "label": "Coverage", "href": "/coverage/" }
20
- ]
4
+ "block": {
5
+ "$type": "component",
6
+ "component": "site-footer",
7
+ "props": {
8
+ "brandHref": "/",
9
+ "brandLabel": "Meridian",
10
+ "brandLogo": {
11
+ "src": "/assets/meridian-logo.svg",
12
+ "alt": "Meridian"
21
13
  },
22
- {
23
- "id": "solutions",
24
- "title": "Solutions",
25
- "links": [
26
- { "id": "business", "label": "Business", "href": "/business/" },
27
- { "id": "maritime", "label": "Maritime", "href": "/business/maritime/" },
28
- { "id": "aerospace", "label": "Aerospace", "href": "/business/aerospace/" }
29
- ]
30
- },
31
- {
32
- "id": "resources",
33
- "title": "Resources",
34
- "links": [
35
- { "id": "blog", "label": "Blog", "href": "/blog/" },
36
- { "id": "documentation", "label": "Documentation", "href": "/docs/" },
37
- { "id": "support", "label": "Support", "href": "/support/" }
38
- ]
39
- },
40
- {
41
- "id": "company",
42
- "title": "Company",
43
- "links": [
44
- { "id": "about", "label": "About us", "href": "/about/" },
45
- { "id": "careers", "label": "Careers", "href": "/careers/" },
46
- { "id": "contact", "label": "Contact", "href": "/contact/" }
47
- ]
48
- }
49
- ],
50
- "social": [
51
- { "id": "x", "label": "X", "href": "https://x.com/meridian", "icon": "X" },
52
- { "id": "linkedin", "label": "LinkedIn", "href": "https://www.linkedin.com/company/meridian", "icon": "in" },
53
- { "id": "youtube", "label": "YouTube", "href": "https://www.youtube.com/meridian", "icon": "YT" }
54
- ],
55
- "legalLinks": [
56
- { "id": "terms", "label": "Terms of Service", "href": "/terms/" },
57
- { "id": "privacy", "label": "Privacy Policy", "href": "/privacy/" },
58
- { "id": "security", "label": "Security", "href": "/security/" }
59
- ],
60
- "copyright": "Copyright (c) 2026 Meridian Satellite Networks, Inc. All rights reserved."
14
+ "columns": [
15
+ {
16
+ "id": "products",
17
+ "title": "Products",
18
+ "links": [
19
+ { "id": "plans", "label": "Plans", "href": "/plans/" },
20
+ { "id": "devices", "label": "Devices", "href": "/devices/" },
21
+ { "id": "coverage", "label": "Coverage", "href": "/coverage/" }
22
+ ]
23
+ },
24
+ {
25
+ "id": "solutions",
26
+ "title": "Solutions",
27
+ "links": [
28
+ { "id": "business", "label": "Business", "href": "/business/" },
29
+ { "id": "maritime", "label": "Maritime", "href": "/business/maritime/" },
30
+ { "id": "aerospace", "label": "Aerospace", "href": "/business/aerospace/" }
31
+ ]
32
+ },
33
+ {
34
+ "id": "resources",
35
+ "title": "Resources",
36
+ "links": [
37
+ { "id": "blog", "label": "Blog", "href": "/blog/" },
38
+ { "id": "documentation", "label": "Documentation", "href": "/docs/" },
39
+ { "id": "support", "label": "Support", "href": "/support/" }
40
+ ]
41
+ },
42
+ {
43
+ "id": "company",
44
+ "title": "Company",
45
+ "links": [
46
+ { "id": "about", "label": "About us", "href": "/about/" },
47
+ { "id": "careers", "label": "Careers", "href": "/careers/" },
48
+ { "id": "contact", "label": "Contact", "href": "/contact/" }
49
+ ]
50
+ }
51
+ ],
52
+ "social": [
53
+ { "id": "x", "label": "X", "href": "https://x.com/meridian", "icon": "X" },
54
+ { "id": "linkedin", "label": "LinkedIn", "href": "https://www.linkedin.com/company/meridian", "icon": "in" },
55
+ { "id": "youtube", "label": "YouTube", "href": "https://www.youtube.com/meridian", "icon": "YT" }
56
+ ],
57
+ "legalLinks": [
58
+ { "id": "terms", "label": "Terms of Service", "href": "/terms/" },
59
+ { "id": "privacy", "label": "Privacy Policy", "href": "/privacy/" },
60
+ { "id": "security", "label": "Security", "href": "/security/" }
61
+ ],
62
+ "copyright": "Copyright (c) 2026 Meridian Satellite Networks, Inc. All rights reserved."
63
+ }
61
64
  }
62
65
  }
@@ -11,6 +11,7 @@
11
11
  "regions": {
12
12
  "main": [
13
13
  {
14
+ "$type": "component",
14
15
  "id": "not-found-hero",
15
16
  "component": "home_hero",
16
17
  "props": {
@@ -11,6 +11,7 @@
11
11
  "regions": {
12
12
  "main": [
13
13
  {
14
+ "$type": "component",
14
15
  "id": "about-hero",
15
16
  "component": "home_hero",
16
17
  "props": {
@@ -27,12 +28,14 @@
27
28
  }
28
29
  },
29
30
  {
31
+ "$type": "component",
30
32
  "id": "about-actions",
31
33
  "component": "container",
32
34
  "props": {
33
35
  "className": "items-center justify-center text-center",
34
36
  "content": [
35
37
  {
38
+ "$type": "component",
36
39
  "id": "home-link",
37
40
  "component": "button",
38
41
  "props": {
@@ -11,6 +11,7 @@
11
11
  "regions": {
12
12
  "main": [
13
13
  {
14
+ "$type": "component",
14
15
  "id": "error-hero",
15
16
  "component": "home_hero",
16
17
  "props": {
@@ -11,6 +11,7 @@
11
11
  "regions": {
12
12
  "main": [
13
13
  {
14
+ "$type": "component",
14
15
  "id": "hero",
15
16
  "component": "home_hero",
16
17
  "props": {
@@ -27,6 +28,7 @@
27
28
  }
28
29
  },
29
30
  {
31
+ "$type": "component",
30
32
  "id": "why-meridian",
31
33
  "component": "feature_grid",
32
34
  "props": {
@@ -34,6 +36,7 @@
34
36
  "subtitle": "Engineered for the edges of the world.",
35
37
  "features": [
36
38
  {
39
+ "$type": "component",
37
40
  "id": "global-coverage",
38
41
  "component": "feature_card",
39
42
  "props": {
@@ -43,6 +46,7 @@
43
46
  }
44
47
  },
45
48
  {
49
+ "$type": "component",
46
50
  "id": "low-latency",
47
51
  "component": "feature_card",
48
52
  "props": {
@@ -52,6 +56,7 @@
52
56
  }
53
57
  },
54
58
  {
59
+ "$type": "component",
55
60
  "id": "easy-setup",
56
61
  "component": "feature_card",
57
62
  "props": {
@@ -64,6 +69,7 @@
64
69
  }
65
70
  },
66
71
  {
72
+ "$type": "component",
67
73
  "id": "stats",
68
74
  "component": "stats_bar",
69
75
  "props": {
@@ -92,6 +98,7 @@
92
98
  }
93
99
  },
94
100
  {
101
+ "$type": "component",
95
102
  "id": "cta",
96
103
  "component": "container",
97
104
  "props": {
@@ -99,6 +106,7 @@
99
106
  "className": "items-center justify-center text-center",
100
107
  "content": [
101
108
  {
109
+ "$type": "component",
102
110
  "id": "cta-primary",
103
111
  "component": "button",
104
112
  "props": {
@@ -110,6 +118,7 @@
110
118
  }
111
119
  },
112
120
  {
121
+ "$type": "component",
113
122
  "id": "cta-secondary",
114
123
  "component": "button",
115
124
  "props": {
@@ -0,0 +1,55 @@
1
+ {
2
+ "$type": "componentPattern",
3
+ "buildId": "codebuild_01a01331-b53d-7bab-a900-d3cef26465d6",
4
+ "codeDigest": "sha256:827b1337ab20e5c054419de4e66f6ffedfc210ca1c34dfdee6c23091edd69d02",
5
+ "name": "button",
6
+ "schema": {
7
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
8
+ "title": "button",
9
+ "type": "object",
10
+ "properties": {
11
+ "label": {
12
+ "type": "string",
13
+ "minLength": 1
14
+ },
15
+ "href": {
16
+ "type": "string",
17
+ "minLength": 1
18
+ },
19
+ "variant": {
20
+ "type": "string",
21
+ "enum": [
22
+ "primary",
23
+ "secondary",
24
+ "tertiary",
25
+ "outline",
26
+ "ghost"
27
+ ]
28
+ },
29
+ "newWindow": {
30
+ "type": "boolean"
31
+ },
32
+ "icon": {
33
+ "type": "string"
34
+ },
35
+ "iconPosition": {
36
+ "type": "string",
37
+ "enum": [
38
+ "before",
39
+ "after"
40
+ ]
41
+ },
42
+ "className": {
43
+ "type": "string"
44
+ }
45
+ },
46
+ "required": [
47
+ "label",
48
+ "href"
49
+ ],
50
+ "additionalProperties": false
51
+ },
52
+ "imageSlots": {},
53
+ "notes": "",
54
+ "skills": []
55
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "$type": "componentPattern",
3
+ "buildId": "codebuild_01a01331-b53d-7bab-a900-d3cef26465d6",
4
+ "codeDigest": "sha256:827b1337ab20e5c054419de4e66f6ffedfc210ca1c34dfdee6c23091edd69d02",
5
+ "name": "container",
6
+ "schema": {
7
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
8
+ "title": "container",
9
+ "type": "object",
10
+ "properties": {
11
+ "theme": {
12
+ "type": "string",
13
+ "enum": [
14
+ "auto",
15
+ "light",
16
+ "dark"
17
+ ]
18
+ },
19
+ "className": {
20
+ "type": "string"
21
+ },
22
+ "content": {
23
+ "type": "array",
24
+ "items": {
25
+ "type": "object",
26
+ "properties": {
27
+ "$type": {
28
+ "type": "string",
29
+ "const": "component"
30
+ },
31
+ "id": {
32
+ "type": "string",
33
+ "minLength": 1
34
+ },
35
+ "component": {
36
+ "type": "string",
37
+ "minLength": 1
38
+ },
39
+ "props": {
40
+ "type": "object",
41
+ "propertyNames": {
42
+ "type": "string"
43
+ },
44
+ "additionalProperties": {}
45
+ }
46
+ },
47
+ "required": [
48
+ "$type",
49
+ "id",
50
+ "component",
51
+ "props"
52
+ ],
53
+ "additionalProperties": false
54
+ }
55
+ }
56
+ },
57
+ "additionalProperties": false
58
+ },
59
+ "imageSlots": {},
60
+ "notes": "",
61
+ "skills": []
62
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "$type": "componentPattern",
3
+ "buildId": "codebuild_01a01331-b53d-7bab-a900-d3cef26465d6",
4
+ "codeDigest": "sha256:827b1337ab20e5c054419de4e66f6ffedfc210ca1c34dfdee6c23091edd69d02",
5
+ "name": "feature_card",
6
+ "schema": {
7
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
8
+ "title": "feature_card",
9
+ "type": "object",
10
+ "properties": {
11
+ "icon": {
12
+ "type": "string"
13
+ },
14
+ "title": {
15
+ "type": "string",
16
+ "minLength": 1
17
+ },
18
+ "description": {
19
+ "type": "string"
20
+ }
21
+ },
22
+ "required": [
23
+ "title"
24
+ ],
25
+ "additionalProperties": false
26
+ },
27
+ "imageSlots": {},
28
+ "notes": "",
29
+ "skills": []
30
+ }