@gradial/aci 0.1.52-rc.8 → 0.1.52

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 (254) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +86 -5
  3. package/dist/astro/error-page.js +1 -0
  4. package/dist/astro/index.js +4 -0
  5. package/dist/astro/page.d.ts +5 -2
  6. package/dist/astro/page.js +35 -23
  7. package/dist/astro/preview.js +2 -17
  8. package/dist/audience-data-models.d.ts +308 -197
  9. package/dist/audience-data-models.js +313 -51
  10. package/dist/cli/compile-registry.mjs +6 -34
  11. package/dist/cli/mintlify-render.mjs +8 -0
  12. package/dist/collect/aci.js +1 -1
  13. package/dist/collect/aci.js.sha256 +1 -1
  14. package/dist/collect/api.d.ts +6 -7
  15. package/dist/collect/api.js +21 -8
  16. package/dist/collect/assignments.d.ts +7 -9
  17. package/dist/collect/assignments.js +23 -32
  18. package/dist/collect/batcher.d.ts +9 -7
  19. package/dist/collect/batcher.js +6 -5
  20. package/dist/collect/consent/envelope.d.ts +7 -1
  21. package/dist/collect/consent/envelope.js +3 -1
  22. package/dist/collect/consent/mapping.d.ts +2 -0
  23. package/dist/collect/consent/mapping.js +1 -0
  24. package/dist/collect/consenthold.d.ts +38 -0
  25. package/dist/collect/consenthold.js +82 -0
  26. package/dist/collect/declarative.js +7 -5
  27. package/dist/collect/gapbuffer.d.ts +13 -4
  28. package/dist/collect/gapbuffer.js +21 -10
  29. package/dist/collect/index.js +78 -21
  30. package/dist/collect/pageviews.d.ts +11 -11
  31. package/dist/collect/pageviews.js +27 -22
  32. package/dist/consent-banner/index.d.ts +17 -0
  33. package/dist/consent-banner/index.js +23 -0
  34. package/dist/content/index.d.ts +2 -1
  35. package/dist/content/index.js +4 -1
  36. package/dist/content/provider.d.ts +16 -2
  37. package/dist/content/provider.js +5 -1
  38. package/dist/content/resolve-slots.d.ts +24 -2
  39. package/dist/content/resolve-slots.js +127 -21
  40. package/dist/content/types.d.ts +5 -1
  41. package/dist/edge/expression-capabilities.v1.d.ts +106 -0
  42. package/dist/edge/expression-capabilities.v1.js +165 -0
  43. package/dist/index.d.ts +1 -2
  44. package/dist/index.js +0 -1
  45. package/dist/internal/domain/edge/identity.d.ts +47 -4
  46. package/dist/internal/domain/edge/identity.js +130 -26
  47. package/dist/internal/domain/edge/index.d.ts +1 -1
  48. package/dist/internal/domain/edge/index.js +1 -1
  49. package/dist/internal/domain/edge/mimic_token.d.ts +14 -0
  50. package/dist/internal/domain/edge/mimic_token.js +125 -0
  51. package/dist/internal/domain/edge/personalization-core.d.ts +61 -22
  52. package/dist/internal/domain/edge/personalization-core.js +790 -200
  53. package/dist/internal/domain/edge/personalization_index.d.ts +17 -0
  54. package/dist/internal/domain/edge/personalization_index.js +55 -0
  55. package/dist/internal/domain/edge/resolve.js +76 -39
  56. package/dist/internal/domain/edge/testdata/flat_only_validator_v1.d.ts +10 -0
  57. package/dist/internal/domain/edge/testdata/flat_only_validator_v1.js +253 -0
  58. package/dist/internal/domain/edge/types.d.ts +82 -78
  59. package/dist/internal/domain/edge/types.js +3 -3
  60. package/dist/internal/domain/edge/variant_key.d.ts +4 -0
  61. package/dist/internal/domain/edge/variant_key.js +56 -0
  62. package/dist/internal/platform/collectionstore/errors.d.ts +24 -0
  63. package/dist/internal/platform/collectionstore/errors.js +48 -0
  64. package/dist/mintlify/blocks.d.ts +6 -0
  65. package/dist/mintlify/blocks.js +201 -0
  66. package/dist/mintlify/config.d.ts +3 -0
  67. package/dist/mintlify/config.js +77 -0
  68. package/dist/mintlify/index.d.ts +3 -0
  69. package/dist/mintlify/index.js +2 -0
  70. package/dist/mintlify/json.d.ts +8 -0
  71. package/dist/mintlify/json.js +37 -0
  72. package/dist/mintlify/materialize.d.ts +14 -0
  73. package/dist/mintlify/materialize.js +254 -0
  74. package/dist/mintlify/types.d.ts +74 -0
  75. package/dist/mintlify/types.js +1 -0
  76. package/dist/mintlify/validate.d.ts +7 -0
  77. package/dist/mintlify/validate.js +256 -0
  78. package/dist/next/assignments-navigation.d.ts +4 -0
  79. package/dist/next/assignments-navigation.js +18 -0
  80. package/dist/next/assignments-script.d.ts +3 -2
  81. package/dist/next/assignments-script.js +5 -5
  82. package/dist/next/error-pages.d.ts +4 -0
  83. package/dist/next/error-pages.js +4 -0
  84. package/dist/next/error-seat-payload.d.ts +2 -1
  85. package/dist/next/error-seat-payload.js +14 -24
  86. package/dist/next/error-seat.js +7 -2
  87. package/dist/next/index.d.ts +1 -1
  88. package/dist/next/index.js +3 -1
  89. package/dist/next/page.d.ts +2 -1
  90. package/dist/next/page.js +15 -4
  91. package/dist/next/release-context.d.ts +30 -0
  92. package/dist/next/release-context.js +56 -0
  93. package/dist/next/render.d.ts +1 -1
  94. package/dist/next/render.js +15 -5
  95. package/dist/next/server.d.ts +9 -3
  96. package/dist/next/server.js +17 -32
  97. package/dist/preview/core.d.ts +1 -0
  98. package/dist/preview/core.js +27 -0
  99. package/dist/react/VideoPlayer.d.ts +3 -3
  100. package/dist/react/index.d.ts +3 -3
  101. package/dist/registry.d.ts +4 -2
  102. package/dist/testing/conformance/checks.d.ts +2 -2
  103. package/dist/testing/conformance/checks.js +2 -0
  104. package/dist/testing/conformance/index.d.ts +1 -0
  105. package/dist/testing/conformance/index.js +1 -0
  106. package/dist/testing/conformance/next-stubs.d.ts +6 -0
  107. package/dist/testing/conformance/next-stubs.js +9 -0
  108. package/dist/testing/conformance/preset.d.ts +3 -2
  109. package/dist/testing/conformance/preset.js +15 -8
  110. package/dist/testing/conformance/root-identity.d.ts +31 -0
  111. package/dist/testing/conformance/root-identity.js +361 -0
  112. package/dist/testing/conformance/stubs/next-image.d.ts +7 -0
  113. package/dist/testing/conformance/stubs/next-image.js +5 -0
  114. package/dist/testing/conformance/stubs/next-link.d.ts +7 -0
  115. package/dist/testing/conformance/stubs/next-link.js +4 -0
  116. package/dist/testing/conformance/types.d.ts +5 -2
  117. package/dist/testing/index.d.ts +1 -1
  118. package/dist/testing/index.js +4 -3
  119. package/dist/types/block-ref.js +2 -2
  120. package/dist/types/component.d.ts +9 -0
  121. package/dist/types/config.d.ts +0 -1
  122. package/dist/types/index.d.ts +0 -1
  123. package/dist/types/index.js +0 -1
  124. package/dist/types/page.d.ts +0 -1
  125. package/migrations/conformance-registry.md +22 -0
  126. package/migrations/experiment-goal-ids.md +43 -0
  127. package/package.json +21 -7
  128. package/src/cli/compile-registry.mjs +6 -30
  129. package/src/cli/mintlify-render.mjs +9 -0
  130. package/src/types/block-ref.test.ts +16 -1
  131. package/src/types/block-ref.ts +2 -2
  132. package/src/types/component.ts +10 -0
  133. package/src/types/config.ts +0 -1
  134. package/src/types/index.ts +0 -1
  135. package/src/types/page.ts +0 -1
  136. package/templates/astro/starter-content/src/components/chrome/Footer.astro +5 -3
  137. package/templates/astro/starter-content/src/components/chrome/Header.astro +5 -3
  138. package/templates/astro/starter-content/src/components/elements/Button.astro +5 -2
  139. package/templates/astro/starter-content/src/components/elements/ButtonBlock.astro +5 -3
  140. package/templates/astro/starter-content/src/components/sections/ContainerSection.astro +4 -3
  141. package/templates/astro/starter-content/src/components/sections/FeatureCard.astro +4 -3
  142. package/templates/astro/starter-content/src/components/sections/FeatureGrid.astro +4 -3
  143. package/templates/astro/starter-content/src/components/sections/HomeHero.astro +5 -3
  144. package/templates/astro/starter-content/src/components/sections/PersonalizationChoice.astro +4 -3
  145. package/templates/astro/starter-content/src/components/sections/StatsBar.astro +4 -3
  146. package/templates/astro/starter-content/src/fixtures/sections.ts +1 -1
  147. package/templates/astro/starter-content/src/stories/CompositionStoryFrame.astro +7 -2
  148. package/templates/astro/template/.aci.yaml.tmpl +0 -1
  149. package/templates/astro/template/package.json.tmpl +5 -5
  150. package/templates/astro/template/src/layouts/SiteLayout.astro +2 -2
  151. package/templates/common/.agents/skills/aci/SKILL.md +58 -1
  152. package/templates/common/.agents/skills/authoring/SKILL.md +194 -42
  153. package/templates/common/.agents/skills/authoring/references/block-registry.md +19 -3
  154. package/templates/common/.agents/skills/authoring/references/contracts.md +9 -30
  155. package/templates/common/.agents/skills/authoring/references/repo-structure.md +5 -4
  156. package/templates/common/.agents/skills/authoring/references/sdk-server-functions.md +3 -3
  157. package/templates/common/.agents/skills/dam-delivery/SKILL.md +216 -0
  158. package/templates/common/.agents/skills/dam-delivery/references/permalink-grammar.md +167 -0
  159. package/templates/common/.agents/skills/migration/references/canon-migration.md +18 -67
  160. package/templates/common/.agents/skills/migration/references/selector-map.md +1 -1
  161. package/templates/common/.content/fragments/navbar/_index.json +24 -0
  162. package/templates/common/.content/fragments/site-footer/_index.json +61 -0
  163. package/templates/common/.content/pages/404/_index.json +4 -2
  164. package/templates/common/.content/pages/_index.json +0 -1
  165. package/templates/common/.content/pages/error/_index.json +4 -2
  166. package/templates/common/.content/schemas/site.schema.json +11 -1
  167. package/templates/common/.content/templates/error.json +24 -0
  168. package/templates/common/.content/templates/home.json +28 -0
  169. package/templates/common/.content/templates/site.json +8 -0
  170. package/templates/common/.content/templates/standard.json +24 -0
  171. package/templates/common/.nvmrc +1 -1
  172. package/templates/common/starter-content/.content/config/site.json +1 -1
  173. package/templates/common/starter-content/.content/dam/README.md +74 -0
  174. package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000101}/asset.json +1 -1
  175. package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000102}/asset.json +1 -1
  176. package/templates/common/starter-content/.content/pages/404/_index.json +1 -2
  177. package/templates/common/starter-content/.content/pages/{home/_index.json → _index.json} +14 -3
  178. package/templates/common/starter-content/.content/pages/about/_index.json +2 -2
  179. package/templates/common/starter-content/.content/pages/error/_index.json +1 -2
  180. package/templates/common/starter-content/.content/patterns/components/button.json +2 -2
  181. package/templates/common/starter-content/.content/patterns/components/container.json +8 -3
  182. package/templates/common/starter-content/.content/patterns/components/feature_card.json +2 -2
  183. package/templates/common/starter-content/.content/patterns/components/feature_grid.json +5 -3
  184. package/templates/common/starter-content/.content/patterns/components/home_hero.json +2 -2
  185. package/templates/common/starter-content/.content/patterns/components/personalization_choice.json +2 -2
  186. package/templates/common/starter-content/.content/patterns/components/site-footer.json +2 -2
  187. package/templates/common/starter-content/.content/patterns/components/site-navigation.json +2 -2
  188. package/templates/common/starter-content/.content/patterns/components/stats_bar.json +2 -2
  189. package/templates/common/starter-content/.content/schemas/site.schema.json +11 -1
  190. package/templates/nextjs/starter-content/src/components/chrome/Footer.tsx +4 -3
  191. package/templates/nextjs/starter-content/src/components/chrome/Header.tsx +4 -3
  192. package/templates/nextjs/starter-content/src/components/elements/Button.tsx +4 -3
  193. package/templates/nextjs/starter-content/src/components/elements/ButtonBlock.tsx +4 -3
  194. package/templates/nextjs/starter-content/src/components/sections/ContainerSection.tsx +4 -2
  195. package/templates/nextjs/starter-content/src/components/sections/FeatureCard.tsx +4 -3
  196. package/templates/nextjs/starter-content/src/components/sections/FeatureGrid.tsx +4 -2
  197. package/templates/nextjs/starter-content/src/components/sections/HomeHero.tsx +4 -3
  198. package/templates/nextjs/starter-content/src/components/sections/PersonalizationChoice.tsx +4 -2
  199. package/templates/nextjs/starter-content/src/components/sections/StatsBar.tsx +4 -3
  200. package/templates/nextjs/template/.aci.yaml +0 -1
  201. package/templates/nextjs/template/.aci.yaml.tmpl +0 -1
  202. package/templates/nextjs/template/package.json.tmpl +4 -3
  203. package/templates/nextjs/template/src/app/_components/c15t-bridge.ts +30 -0
  204. package/templates/nextjs/template/src/app/_components/c15t-consent-ui.tsx +37 -0
  205. package/templates/nextjs/template/src/app/_components/consent-manager.css +63 -0
  206. package/templates/nextjs/template/src/app/_components/consent-manager.tsx +76 -0
  207. package/templates/nextjs/template/src/app/fonts/inter/OFL.txt +93 -0
  208. package/templates/nextjs/template/src/app/fonts/inter/inter-latin.woff2 +0 -0
  209. package/templates/nextjs/template/src/app/fonts/space-grotesk/OFL.txt +93 -0
  210. package/templates/nextjs/template/src/app/fonts/space-grotesk/space-grotesk-latin.woff2 +0 -0
  211. package/templates/nextjs/template/src/app/layout.tsx +12 -7
  212. package/templates/nextjs/template/tests/conformance.test.ts.tmpl +1 -0
  213. package/dist/define-layout.d.ts +0 -6
  214. package/dist/define-layout.js +0 -14
  215. package/dist/edge/personalization-core.d.ts +0 -11
  216. package/dist/edge/personalization-core.js +0 -269
  217. package/dist/internal/domain/edge/compose.d.ts +0 -24
  218. package/dist/internal/domain/edge/compose.js +0 -89
  219. package/dist/internal/domain/edge/record/codec.d.ts +0 -71
  220. package/dist/internal/domain/edge/record/codec.js +0 -401
  221. package/dist/next/collect-route.d.ts +0 -19
  222. package/dist/next/collect-route.js +0 -459
  223. package/dist/next/edge-config.d.ts +0 -1
  224. package/dist/next/edge-config.js +0 -92
  225. package/dist/next/envelope-digest.d.ts +0 -21
  226. package/dist/next/envelope-digest.js +0 -58
  227. package/dist/next/middleware.d.ts +0 -40
  228. package/dist/next/middleware.js +0 -475
  229. package/dist/next/personalization-host.d.ts +0 -134
  230. package/dist/next/personalization-host.js +0 -270
  231. package/dist/next/root-layout.d.ts +0 -4
  232. package/dist/next/root-layout.js +0 -5
  233. package/dist/next/vercel-collect-sdk-route.d.ts +0 -27
  234. package/dist/next/vercel-collect-sdk-route.js +0 -181
  235. package/dist/next/vercel-edge-projection-v1.d.ts +0 -24
  236. package/dist/next/vercel-edge-projection-v1.js +0 -880
  237. package/dist/next/vercel-personalization.d.ts +0 -4
  238. package/dist/next/vercel-personalization.js +0 -4
  239. package/dist/next/vercel-s3-personalization.d.ts +0 -79
  240. package/dist/next/vercel-s3-personalization.js +0 -570
  241. package/dist/types/layout.d.ts +0 -24
  242. package/src/types/layout.ts +0 -29
  243. package/templates/astro/starter-content/src/render/RenderPage.astro +0 -67
  244. package/templates/astro/template/src/cms/contracts/layouts/index.ts +0 -18
  245. package/templates/common/starter-content/.content/patterns/layouts/default.json +0 -14
  246. package/templates/nextjs/starter-content/src/components/elements/AtomicBlock.tsx +0 -13
  247. package/templates/nextjs/template/src/cms/contracts/layouts/index.ts +0 -16
  248. /package/dist/{types/layout.js → cli/mintlify-render.d.mts} +0 -0
  249. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000101}/editorial.json +0 -0
  250. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000101}/governance.json +0 -0
  251. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000101}/versions/v0001.json +0 -0
  252. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000102}/editorial.json +0 -0
  253. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000102}/governance.json +0 -0
  254. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000102}/versions/v0001.json +0 -0
@@ -0,0 +1,216 @@
1
+ ---
2
+ name: dam-delivery
3
+ description: Build and use public DAM asset links (`/dam/{assetId}`) with on-the-fly image shaping (resize, crop, format, quality). Use when a page, a CMS field, or an email needs a URL for an approved DAM asset, when choosing between pre-baked derivatives and a shaped public link, when making an asset public or taking it down, or when a `/dam/…` URL answers 301, 403, or 404 unexpectedly.
4
+ ---
5
+
6
+ # DAM Delivery — Public Asset Links
7
+
8
+ Every approved DAM asset has one public address on the site's own domain:
9
+
10
+ ```
11
+ https://{site}/dam/{assetId}
12
+ ```
13
+
14
+ Add a query to shape an image on the fly:
15
+
16
+ ```
17
+ https://{site}/dam/{assetId}?w=640&h=384&fit=crop&fm=jpg&q=80
18
+ ```
19
+
20
+ The link is built from the asset ID alone. Nothing is minted or signed and
21
+ nothing expires, so the same link can sit in a page, a CMS field, or an
22
+ email for years. It always serves the newest approved public version of
23
+ the asset.
24
+
25
+ ## Retired — Do Not Use
26
+
27
+ Earlier SDK releases documented two delivery lanes that no longer exist.
28
+ If you meet any of these in old content, notes, or code, replace them with
29
+ the link above. They are not served and they do not redirect.
30
+
31
+ | Retired | Now |
32
+ | --- | --- |
33
+ | Signed URLs under `/dam/i/…`, `POST /v1/dam/assets/{assetId}/delivery-urls`, `aci dam delivery-url` | Build `/dam/{assetId}?…` yourself; there is nothing to mint |
34
+ | Permalinks under `/dam/a/…`, `GET /v1/dam/assets/{assetId}/permalinks`, recipe URLs | `/dam/{assetId}?…` |
35
+ | The `lifecycle.publicDelivery` flag | Approving a version makes it public (see below) |
36
+ | `width`, `height`, `gravity`, `dpr`, `quality`, `format` | `w`, `h`, `fit`, `fm`, `q` |
37
+
38
+ ## The Link
39
+
40
+ ```
41
+ /dam/{assetId}[/{slug}][?w=…&h=…&fit=…&fm=…&q=…]
42
+ ```
43
+
44
+ - **`assetId`** is a raw 27-character ID made of digits and letters
45
+ (`0-9A-Za-z`), with no prefix. IDs that start with `ast_` are retired
46
+ and rejected; per ADR 0043 they are replaced by the breaking migration,
47
+ with no compatibility period.
48
+ - **`slug`** is optional and cosmetic: `/dam/{assetId}/spring-sale.jpg`
49
+ answers with a permanent redirect to `/dam/{assetId}`, keeping the query.
50
+ Use it when a tool insists on a URL that ends in a file extension. The
51
+ slug is never checked against anything, and its extension does not pick
52
+ the format.
53
+ - The CLI prints the path for an ID. It is a formatter only: it makes no
54
+ API call and does not tell you whether the asset is public.
55
+
56
+ ```bash
57
+ aci dam permalink 2E9qL0001aBcDeFgHiJkLmNoPqR
58
+ # /dam/2E9qL0001aBcDeFgHiJkLmNoPqR
59
+ ```
60
+
61
+ The route is served by the deployed site's hosting edge. The site's own
62
+ dev server (`npm run dev`) only serves compiled derivatives under
63
+ `/dam/…`, so a public link answers 404 there — test it against the
64
+ deployed domain.
65
+
66
+ ## Shaping an Image
67
+
68
+ Five query parameters, and only these. Full rules and worked examples:
69
+ `references/permalink-grammar.md`.
70
+
71
+ | Param | Values | Default | Notes |
72
+ | --- | --- | --- | --- |
73
+ | `w`, `h` | whole numbers | none | held to 16–3840, then snapped down to a fixed ladder of sizes |
74
+ | `fit` | `max` \| `crop` | `max` | `max` fits inside the box; `crop` fills it and needs both `w` and `h` |
75
+ | `fm` | `webp` \| `avif` \| `jpg` \| `png` | `webp` | the format is always this parameter, never the browser's `Accept` header |
76
+ | `q` | 5–100 | 80 | snapped down to steps of 5 |
77
+
78
+ Three things to know before writing a link:
79
+
80
+ - **Any one parameter turns shaping on, and the rest take their
81
+ defaults.** `?w=640` serves WebP at quality 80, not the original format.
82
+ To keep a JPEG a JPEG, say `fm=jpg`.
83
+ - **No parameters means the original.** `/dam/{assetId}` streams the
84
+ approved file byte for byte.
85
+ - **Nothing is rejected.** Unknown parameter names (`utm_*`, `mkt_tok`,
86
+ cache busters) are dropped. Out-of-range or unrecognised values are
87
+ snapped to an allowed one, sizes and quality always downward: `w=333`
88
+ serves 256, `q=77` serves 75, `fm=gif` serves WebP. Do not expect
89
+ pixel-exact sizes off the ladder. A `w`, `h`, or `q` that is not a
90
+ whole number (`w=abc`, `q=high`) is treated as if it had been left out,
91
+ so a typo never shrinks an image or lowers its quality.
92
+
93
+ The edge settles every link on one canonical form: the parameters in the
94
+ order `w`, `h`, `fit`, `fm`, `q`, after snapping, with `fit`, `fm`, and
95
+ `q` always present. A link written any other way still works. A request
96
+ already on `/dam/{assetId}` with a noncanonical query is answered with one
97
+ permanent redirect to the canonical link (sites hosted on CloudFront tidy
98
+ the query without a visible redirect). A request carrying a slug and a
99
+ noncanonical query can redirect twice: once to drop the slug, once to
100
+ canonicalize the query. Write the canonical form when you control the URL
101
+ and you skip those hops.
102
+
103
+ Shaping only applies to JPEG, PNG, WebP, and AVIF originals. Everything
104
+ else, SVG included, is served only as a file download, never displayed
105
+ inline. On AWS CloudFront this holds only once the hosting fix (PR #709)
106
+ has rolled out to the tenant; until then that lane may serve SVG or HTML
107
+ inline. See the reference for the size limits.
108
+
109
+ ## What Makes a Link Serve
110
+
111
+ Approving a version is the "make public" act. There is no separate flag.
112
+
113
+ ```
114
+ POST /v1/dam/assets/{assetId}/lifecycle
115
+ { "action": "approve", "versionId": "…", "expectedHead": "…" }
116
+ ```
117
+
118
+ Read `effects` in the response to see what happened:
119
+
120
+ | Effect | Meaning |
121
+ | --- | --- |
122
+ | `live_published` | the link now serves this version |
123
+ | `public_admission_vetoed:<cause>` | the version is approved but **not public**: its expiry date has already passed, an embargo is still in force, a usage block, or a license that does not allow publication. A future expiry date does not veto; the link serves until that date. The link keeps serving the previous public version, if there is one |
124
+ | `live_plane_unconfigured` | a local or dev stack with no delivery wired; nothing was made public |
125
+
126
+ On the asset record (`GET /v1/dam/assets/{assetId}`), a public version
127
+ shows `deliveryTier: "public"` in its governance, and a refused one shows
128
+ `tierAdmissionVeto`.
129
+
130
+ - **A newer approved version takes over the same link.** Nothing needs
131
+ re-pasting.
132
+ - **To take a link down**, `demote` the version (the link falls back to
133
+ the previous public version, or stops serving if there is none) or
134
+ `archive` the asset. Both use the same lifecycle endpoint.
135
+ - **Governance that would have blocked the approval cannot be added while
136
+ a version is public.** A patch adding a past expiry date, an embargo, a
137
+ usage block, or a license without publication is refused with a
138
+ conflict that tells you to demote first. A future expiry date can be
139
+ set, moved, or cleared on a public version; the link stops serving the
140
+ version when that date arrives.
141
+ - **Changes take up to five minutes to show everywhere.** Responses are
142
+ `Cache-Control: public, max-age=300`, so a new version or a take-down
143
+ can lag by that long in browsers and CDN caches. On AWS CloudFront,
144
+ cached copies can currently outlive that window — the image pipeline
145
+ sets a one-year TTL and the distribution permits it — until the hosting
146
+ lane is corrected; the five minutes holds for Vercel and Cloudflare. Do
147
+ not treat a public link as instantly revocable, and do not re-cache it
148
+ for longer downstream.
149
+
150
+ ## Pre-Baked Derivatives or a Public Link?
151
+
152
+ - **Images a page renders from content: keep using the pre-baked
153
+ pipeline.** Declare `imageSlots` in the contract and render with
154
+ `imageAttrs()` or `Picture`. The build produces fixed sizes per slot and
155
+ you never construct a URL. See the authoring skill.
156
+ - **A URL that lives outside the build: use the public link.** A field in
157
+ an external CMS, an email, a social card, a partner page, or a one-off
158
+ size that no slot declares.
159
+
160
+ The difference that matters: a pre-baked image is fixed at publish time
161
+ and changes with the next publish. A public link follows approval and
162
+ take-down on its own, within the five-minute window.
163
+
164
+ Published content may carry `/dam/{assetId}` links. The publish check
165
+ rejects a payload that contains a raw source path (`dam/blobs/…`) or a
166
+ retired `/dam/a/` or `/dam/i/` path.
167
+
168
+ ## Email and External CMS Fields
169
+
170
+ 1. **Paste the link; never let the tool import the bytes.** Many email
171
+ tools offer to "import" or "copy" images to their own CDN. Refuse. An
172
+ imported copy escapes approval and take-down forever; the link is the
173
+ governed pointer.
174
+ 2. **Always set `fm=jpg` or `fm=png` in email.** The default is WebP, and
175
+ Outlook desktop cannot render WebP.
176
+ 3. **Use the slug form when a tool wants a file extension:**
177
+ `/dam/{assetId}/spring-sale.jpg?w=640&fm=jpg`.
178
+ 4. **`block_new_usage` does not take a public link down.** The link cannot
179
+ tell an old placement from a new one, so an asset under that
180
+ restriction keeps serving where it is already pasted. To stop serving,
181
+ demote or archive.
182
+
183
+ ## When a Link Does Not Serve
184
+
185
+ Errors are never cached (`no-store`) on Vercel and Cloudflare. On AWS
186
+ CloudFront, 403 and 5xx responses may be cached by the distribution for up
187
+ to about ten seconds regardless of `no-store`. Exact codes differ slightly
188
+ between hosting providers, so treat any 4xx as "not served".
189
+
190
+ | You see | Why | Do |
191
+ | --- | --- | --- |
192
+ | 301 | a slug was dropped or the query was put in canonical form | follow it; write the canonical link next time |
193
+ | 403 | the asset has no public version (never approved, approval vetoed, demoted, archived, or expired), or the ID is malformed | check `effects` from the approve call and `deliveryTier` on the asset record |
194
+ | 404 | a retired `/dam/a/…` or `/dam/i/…` path, DAM delivery is not enabled for this site, or you are on the local dev server | rewrite the link; ask the operator to enable DAM delivery |
195
+ | 413 | the original is too large to shape | drop the query to get the original, or upload a smaller master |
196
+ | 415 | shaping was asked of a non-image original | drop the query |
197
+ | 503 | delivery could not read its own state and refused rather than guess | retry; if it persists, it is an operator issue |
198
+ | A download instead of an image | the original is not JPEG, PNG, WebP, or AVIF | by design; render SVGs through the site's own image pipeline |
199
+
200
+ ## Draft Previews (Management Surfaces Only)
201
+
202
+ The public link never serves a draft. A draft or rejected version is
203
+ reachable only by an authenticated caller with read permission on the
204
+ asset, and only by naming the exact version:
205
+
206
+ ```
207
+ GET /v1/dam/assets/{assetId}/versions/{versionId}/preview
208
+ ```
209
+
210
+ It answers `302` to a short-lived signed URL: for JPEG, PNG, GIF, and WebP
211
+ up to 20 MiB, a WebP of that version downsampled at upload (longest edge
212
+ 1024 px, never upscaled); for SVG, formats the server does not decode
213
+ (HEIC, AVIF, TIFF, BMP), or a version whose preview is not generated yet,
214
+ the original. Follow the redirect without forwarding your bearer token. It
215
+ takes no shaping parameters. Use it for editorial UIs and thumbnails,
216
+ never for site rendering.
@@ -0,0 +1,167 @@
1
+ # Public Link Grammar Reference (`/dam/{assetId}`)
2
+
3
+ The one public address for a DAM asset:
4
+
5
+ ```
6
+ /dam/{assetId}[/{slug}]?w=640&h=384&fit=crop&fm=jpg&q=80
7
+ ```
8
+
9
+ Permanent, built from the asset ID alone, and always serving the newest
10
+ approved public version. Everything below is enforced at the hosting edge.
11
+ No API call sits in the path of a view, and no API hands these links out —
12
+ you write them.
13
+
14
+ ## Asset ID
15
+
16
+ Exactly 27 characters from `0-9A-Za-z`, no prefix. IDs that start with
17
+ `ast_` are retired and refused; per ADR 0043 the migration to the new IDs
18
+ is breaking, with no compatibility period. Anything else is refused.
19
+
20
+ ## Parameters
21
+
22
+ Only these five names mean anything. Every other name is dropped, so
23
+ tracking decorations (`utm_*`, `mkt_tok`) cannot break an image.
24
+
25
+ | Param | Values | Default | Snapping |
26
+ | --- | --- | --- | --- |
27
+ | `w`, `h` | whole numbers | none | below 16 becomes 16, above 3840 becomes 3840, otherwise snapped DOWN to the ladder `16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840` |
28
+ | `fit` | `max` \| `crop` | `max` | any other value becomes `max` |
29
+ | `fm` | `webp` \| `avif` \| `jpg` \| `png` | `webp` | any other value becomes `webp` |
30
+ | `q` | whole numbers | 80 | below 5 becomes 5, above 100 becomes 100, otherwise snapped DOWN to a step of 5 |
31
+
32
+ Nothing in the query is ever answered with an error. A value the address
33
+ cannot read is treated as if that parameter had been left out, so a typo
34
+ never shrinks an image or lowers its quality. The reading rules are the
35
+ same on every hosting provider:
36
+
37
+ - **Any of the five names turns shaping on**, whatever its value, even an
38
+ empty one (`?w=`).
39
+ - **`w`, `h`, and `q` are read only as a whole number in plain digits**,
40
+ with an optional leading minus sign. Zero and negative numbers count as
41
+ below the minimum (`w=-1` serves 16, `q=0` serves 5). Anything else is
42
+ read as left out: an empty value, letters (`w=abc`, `w=640px`), a
43
+ decimal point (`w=640.7`), an exponent (`w=1e3`), a plus sign, spaces,
44
+ or a number larger in size than 9007199254740991. A `w` or `h` read as
45
+ left out sets no size; a `q` read as left out is 80.
46
+ - **An unrecognised or empty `fit` or `fm` takes its default** (`max`,
47
+ `webp`).
48
+ - **Names, and the values of `fit` and `fm`, are lowercase only.** `W=640`
49
+ is an unknown name and is dropped; `fm=JPG` serves WebP.
50
+ - **When a name repeats, the first one decides**, even when it cannot be
51
+ read: `?w=abc&w=640` sets no width.
52
+
53
+ The ladder is the Next.js image `srcset` ladder, so breakpoints written
54
+ for any modern framework land on a rung instead of snapping visibly.
55
+ Snapping is deliberate: it keeps the number of distinct renditions per
56
+ asset small and bounded.
57
+
58
+ How `fit` behaves:
59
+
60
+ - **`max`** scales the image to fit inside `w` × `h`, keeps its
61
+ proportions, and never crops. Give one dimension and the other follows.
62
+ - **`crop`** scales and crops to fill `w` × `h` exactly. With only one
63
+ dimension there is nothing to crop to, so it behaves like `max`.
64
+ - Neither enlarges: asking for more pixels than the original has serves
65
+ the original size.
66
+
67
+ Deliberately **not** in the grammar:
68
+
69
+ - No `dpr`. Pick a larger `w` rung instead, the way `srcset` does.
70
+ - No crop position or focal point.
71
+ - No format negotiation from the browser's `Accept` header. The format is
72
+ always the explicit `fm`.
73
+ - No recipes or named presets, no signatures, no expiry.
74
+
75
+ ## Canonical Form
76
+
77
+ One spelling per rendition: `w`, `h`, `fit`, `fm`, `q` in that order,
78
+ after snapping. `w` and `h` appear only when given; `fit`, `fm`, and `q`
79
+ always appear once any of the five is present.
80
+
81
+ | You write | Canonical link |
82
+ | --- | --- |
83
+ | `/dam/{id}?w=640` | `/dam/{id}?w=640&fit=max&fm=webp&q=80` |
84
+ | `/dam/{id}?q=77&w=333&utm_source=mail` | `/dam/{id}?w=256&fit=max&fm=webp&q=75` |
85
+ | `/dam/{id}?fm=jpg` | `/dam/{id}?fit=max&fm=jpg&q=80` (a format change at the original size) |
86
+ | `/dam/{id}?w=640&q=high` | `/dam/{id}?w=640&fit=max&fm=webp&q=80` (the unreadable quality is read as left out) |
87
+ | `/dam/{id}?w=abc` | `/dam/{id}?fit=max&fm=webp&q=80` (no width, but still shaped: WebP at the original size) |
88
+ | `/dam/{id}?utm_source=mail` | `/dam/{id}` (no shaping asked for, so the original) |
89
+ | `/dam/{id}/spring-sale.jpg?w=640` | `/dam/{id}?w=640`, then as the first row |
90
+
91
+ A link that is not canonical is answered with one permanent redirect
92
+ (`301`, cacheable for an hour) to the canonical link, before any image
93
+ work is done. The image itself is cached once, under the canonical link.
94
+ A slug costs its own redirect first, with the query carried over as
95
+ written.
96
+
97
+ Sites hosted on CloudFront put the query in canonical form without a
98
+ visible redirect; a slug still redirects there.
99
+
100
+ ## Responses
101
+
102
+ Only `GET` and `HEAD` are served. Error responses are `no-store` on
103
+ Vercel and Cloudflare; on AWS CloudFront, 403 and 5xx responses may be
104
+ cached by the distribution for up to about ten seconds regardless of
105
+ `no-store`. Exact error codes differ slightly between hosting providers;
106
+ treat any 4xx as "not served".
107
+
108
+ | Response | Meaning |
109
+ | --- | --- |
110
+ | 200, `Cache-Control: public, max-age=300` | serving |
111
+ | 301 | a slug was dropped, or the query was put in canonical form |
112
+ | 403, `no-store` | the asset has no public version (never approved, approval vetoed, demoted, archived, or expired), or the ID is malformed |
113
+ | 404, `no-store` | a retired `/dam/a/…` or `/dam/i/…` path, or DAM delivery is not enabled for the site |
114
+ | 413, `no-store` | the original is too large to shape (limits below) |
115
+ | 415, `no-store` | shaping was asked of an original that is not JPEG, PNG, WebP, or AVIF |
116
+ | 503, `no-store` | delivery could not read its own state; it refuses rather than guess |
117
+
118
+ The five-minute `max-age` is the whole revocation story. After a
119
+ take-down, an already-cached copy can keep showing for up to five minutes;
120
+ the next uncached request is checked again and refused. A newly approved
121
+ version takes over the link within the same window. This holds for Vercel
122
+ and Cloudflare; on AWS CloudFront, cached copies can currently outlive the
123
+ window — the image pipeline sets a one-year TTL and the distribution
124
+ permits it — until the hosting lane is corrected.
125
+
126
+ ## Originals That Are Not Shaped
127
+
128
+ - **No query:** the approved file is streamed byte for byte, whatever its
129
+ size.
130
+ - **Shapeable originals** are JPEG, PNG, WebP, and AVIF.
131
+ - **Everything else** (SVG, GIF, PDF, video, documents) is served only as
132
+ a file download (`Content-Disposition: attachment`, with
133
+ `X-Content-Type-Options: nosniff`) and never displayed inline. An
134
+ `<img>` pointing at such a link will not render. On AWS CloudFront this
135
+ holds only once the hosting fix (PR #709) has rolled out to the tenant;
136
+ until then that lane may serve SVG or HTML inline.
137
+ - **Size limits for shaping:** the original must be at most 20 MB, at most
138
+ 8192 pixels on either side, and at most 40 megapixels. Past any of
139
+ those, a shaped link is refused; the plain link still serves the
140
+ original. Provider gap: the AWS CloudFront lane does not currently
141
+ enforce these bounds, so an over-limit request is not refused there — do
142
+ not rely on enforcement on CloudFront.
143
+
144
+ ## Examples
145
+
146
+ ```html
147
+ <!-- Fixed-size email hero, JPEG for Outlook, explicit quality.
148
+ 640 and 384 are ladder rungs; keep the HTML width/height on the same
149
+ rungs so the served crop and the layout box agree exactly -->
150
+ <img src="https://www.example.com/dam/2E9qL0001aBcDeFgHiJkLmNoPqR?w=640&h=384&fit=crop&fm=jpg&q=80"
151
+ width="640" height="384" alt="Spring sale hero">
152
+
153
+ <!-- Responsive srcset on ladder rungs, canonical form (no dpr — use w) -->
154
+ <img src="https://www.example.com/dam/2E9qL0001aBcDeFgHiJkLmNoPqR?w=828&fit=max&fm=webp&q=80"
155
+ srcset="https://www.example.com/dam/2E9qL0001aBcDeFgHiJkLmNoPqR?w=640&fit=max&fm=webp&q=80 640w,
156
+ https://www.example.com/dam/2E9qL0001aBcDeFgHiJkLmNoPqR?w=828&fit=max&fm=webp&q=80 828w,
157
+ https://www.example.com/dam/2E9qL0001aBcDeFgHiJkLmNoPqR?w=1200&fit=max&fm=webp&q=80 1200w"
158
+ sizes="(max-width: 640px) 100vw, 640px" alt="…">
159
+
160
+ <!-- Slug form for editors that check for a file extension: one cached
161
+ redirect to the link without the slug. The extension is cosmetic;
162
+ fm still picks the format -->
163
+ https://www.example.com/dam/2E9qL0001aBcDeFgHiJkLmNoPqR/spring-sale.jpg?w=640&fit=max&fm=jpg&q=80
164
+ ```
165
+
166
+ Snapping in practice: `?w=333&q=77` and `?w=256&q=75` serve the identical
167
+ rendition (333 snaps to 256, 77 snaps to 75).
@@ -92,29 +92,12 @@ export default function Layout({ children }: { children: ReactNode }) {
92
92
  }
93
93
  ```
94
94
 
95
- ### 7. Layout Contracts: Use SDK Helpers
95
+ ### 7. Replace Layout Contracts With Content Templates
96
96
 
97
- Replace plain object literals with SDK helpers:
98
-
99
- ```ts
100
- // src/cms/contracts/layouts/index.ts
101
- import { defineLayoutContract, slot, fragmentDefault } from '@gradial/aci';
102
-
103
- export default [
104
- defineLayoutContract({
105
- name: 'default',
106
- slots: [
107
- slot('header'),
108
- slot('main', true),
109
- slot('footer'),
110
- ],
111
- defaults: {
112
- header: fragmentDefault('navbar'),
113
- footer: fragmentDefault('site-footer'),
114
- },
115
- }),
116
- ];
117
- ```
97
+ Convert each legacy code layout into `.content/templates/<name>.json`. The
98
+ template owns ordered regions, placement policy, requiredness, and fragment
99
+ defaults. Bind pages with `template` (or a route binding), then delete the
100
+ layout contract and remove `layoutRegistry` from `.aci.yaml`.
118
101
 
119
102
  ### 8. Content Compilation: Standardize CLI Command
120
103
 
@@ -235,23 +218,15 @@ conformancePreset({
235
218
  `dam` rewrite from `next.config.ts`. The SDK asset route handles
236
219
  both dev-mode file serving and deployed S3 serving.
237
220
 
238
- 6. **Delete Runtime `layouts.ts`**
221
+ 6. **Replace All Layout Definitions**
239
222
 
240
- Delete `src/cms/layouts.ts` (runtime layout definition duplicating the
241
- contract-side definition). `createPage` reads layouts from the contract/
242
- compiled data, not a runtime definition.
223
+ Convert `src/cms/layouts.ts` and any `src/cms/contracts/layouts` definition
224
+ into `.content/templates`. Move navbar/footer fragment defaults into the
225
+ template regions, bind pages with `template`, and delete the old files.
243
226
 
244
- 7. **Update Layout Contract Defaults**
227
+ 7. **Remove Retired Config**
245
228
 
246
- Replace raw `{kind: 'fragment-ref' as const, ...}` objects with
247
- `fragmentDefault()`:
248
-
249
- ```ts
250
- defaults: {
251
- header: fragmentDefault('navbar'),
252
- footer: fragmentDefault('site-footer'),
253
- }
254
- ```
229
+ Remove `layoutRegistry` from `.aci.yaml`; current config rejects it.
255
230
 
256
231
  8. **Update `.aci.yaml` siteId**
257
232
 
@@ -272,29 +247,11 @@ conformancePreset({
272
247
 
273
248
  #### Required Changes
274
249
 
275
- 1. **Update Layout Contract Style**
250
+ 1. **Replace Layout Contracts With Templates**
276
251
 
277
- Replace plain object literals with SDK helpers:
278
-
279
- ```ts
280
- // src/cms/contracts/layouts/index.ts
281
- import { defineLayoutContract, slot, fragmentDefault } from '@gradial/aci';
282
-
283
- export default [
284
- defineLayoutContract({
285
- name: 'default',
286
- slots: [
287
- slot('header'),
288
- slot('main', true),
289
- slot('footer'),
290
- ],
291
- defaults: {
292
- header: fragmentDefault('navbar'),
293
- footer: fragmentDefault('site-footer'),
294
- },
295
- }),
296
- ];
297
- ```
252
+ Convert the layout contract into `.content/templates/default.json`, move
253
+ fragment defaults into template regions, bind pages with `template`, and
254
+ delete the code layout registry.
298
255
 
299
256
  2. **Remove Customer Middleware**
300
257
 
@@ -359,16 +316,10 @@ conformancePreset({
359
316
  already in place and handles DAM asset serving automatically with immutable
360
317
  caching (`max-age=31536000`).
361
318
 
362
- 3. **Update Layout Contract Defaults**
319
+ 3. **Move Layout Defaults Into Templates**
363
320
 
364
- Replace raw inline objects with `fragmentDefault()`:
365
-
366
- ```ts
367
- defaults: {
368
- header: fragmentDefault('navbar'),
369
- footer: fragmentDefault('site-footer'),
370
- }
371
- ```
321
+ Move navbar/footer defaults into `.content/templates` regions and delete
322
+ the code layout definition.
372
323
 
373
324
  4. **General Migration Steps**
374
325
 
@@ -13,7 +13,7 @@ Location: `artifacts/source-vs-target/selector-map.json`
13
13
  "targetUrl": "http://localhost:3000/",
14
14
  "breakpoints": [360, 768, 1024, 1280, 1400],
15
15
  "entries": {
16
- "content/pages/home/_index.json blocks[\"home-hero\"]": {
16
+ "content/pages/_index.json blocks[\"home-hero\"]": {
17
17
  "source": ".home-hero",
18
18
  "target": ".home-hero",
19
19
  "viewport": "both",
@@ -0,0 +1,24 @@
1
+ {
2
+ "$type": "fragment",
3
+ "fragmentId": "navbar",
4
+ "block": {
5
+ "$type": "component",
6
+ "component": "site-navigation",
7
+ "props": {
8
+ "brandHref": "/",
9
+ "brandLabel": "Meridian",
10
+ "navigation": [
11
+ { "id": "home", "label": "Home", "href": "/" },
12
+ { "id": "plans", "label": "Plans", "href": "/plans/" },
13
+ { "id": "devices", "label": "Devices", "href": "/devices/" },
14
+ { "id": "business", "label": "Business", "href": "/business/" },
15
+ { "id": "blog", "label": "Blog", "href": "/blog/" },
16
+ { "id": "about", "label": "About", "href": "/about/" }
17
+ ],
18
+ "utilities": [
19
+ { "id": "sign-in", "label": "Sign in", "href": "/sign-in/", "variant": "ghost" },
20
+ { "id": "contact-sales", "label": "Contact sales", "href": "/contact-sales/", "variant": "primary" }
21
+ ]
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "$type": "fragment",
3
+ "fragmentId": "site-footer",
4
+ "block": {
5
+ "$type": "component",
6
+ "component": "site-footer",
7
+ "props": {
8
+ "brandHref": "/",
9
+ "brandLabel": "Meridian",
10
+ "columns": [
11
+ {
12
+ "id": "products",
13
+ "title": "Products",
14
+ "links": [
15
+ { "id": "plans", "label": "Plans", "href": "/plans/" },
16
+ { "id": "devices", "label": "Devices", "href": "/devices/" },
17
+ { "id": "coverage", "label": "Coverage", "href": "/coverage/" }
18
+ ]
19
+ },
20
+ {
21
+ "id": "solutions",
22
+ "title": "Solutions",
23
+ "links": [
24
+ { "id": "business", "label": "Business", "href": "/business/" },
25
+ { "id": "maritime", "label": "Maritime", "href": "/business/maritime/" },
26
+ { "id": "aerospace", "label": "Aerospace", "href": "/business/aerospace/" }
27
+ ]
28
+ },
29
+ {
30
+ "id": "resources",
31
+ "title": "Resources",
32
+ "links": [
33
+ { "id": "blog", "label": "Blog", "href": "/blog/" },
34
+ { "id": "documentation", "label": "Documentation", "href": "/docs/" },
35
+ { "id": "support", "label": "Support", "href": "/support/" }
36
+ ]
37
+ },
38
+ {
39
+ "id": "company",
40
+ "title": "Company",
41
+ "links": [
42
+ { "id": "about", "label": "About us", "href": "/about/" },
43
+ { "id": "careers", "label": "Careers", "href": "/careers/" },
44
+ { "id": "contact", "label": "Contact", "href": "/contact/" }
45
+ ]
46
+ }
47
+ ],
48
+ "social": [
49
+ { "id": "x", "label": "X", "href": "https://x.com/meridian", "icon": "X" },
50
+ { "id": "linkedin", "label": "LinkedIn", "href": "https://www.linkedin.com/company/meridian", "icon": "in" },
51
+ { "id": "youtube", "label": "YouTube", "href": "https://www.youtube.com/meridian", "icon": "YT" }
52
+ ],
53
+ "legalLinks": [
54
+ { "id": "terms", "label": "Terms of Service", "href": "/terms/" },
55
+ { "id": "privacy", "label": "Privacy Policy", "href": "/privacy/" },
56
+ { "id": "security", "label": "Security", "href": "/security/" }
57
+ ],
58
+ "copyright": "Copyright (c) 2026 Meridian Satellite Networks, Inc. All rights reserved."
59
+ }
60
+ }
61
+ }
@@ -2,15 +2,17 @@
2
2
  "$type": "page",
3
3
  "id": "meridian-not-found",
4
4
  "status": "published",
5
- "layout": "default",
6
5
  "metadata": {
7
6
  "title": "Page not found — Meridian",
8
7
  "description": "The page you are looking for does not exist.",
9
- "sitemap": { "visible": false }
8
+ "sitemap": {
9
+ "visible": false
10
+ }
10
11
  },
11
12
  "regions": {
12
13
  "main": [
13
14
  {
15
+ "$type": "component",
14
16
  "id": "not-found-hero",
15
17
  "component": "home_hero",
16
18
  "props": {
@@ -2,7 +2,6 @@
2
2
  "$type": "page",
3
3
  "id": "meridian-home",
4
4
  "status": "published",
5
- "layout": "default",
6
5
  "metadata": {
7
6
  "title": "Meridian — Global Satellite Internet",
8
7
  "description": "Connect from anywhere on Earth. Low-latency satellite internet for homes, businesses, and beyond.",
@@ -2,15 +2,17 @@
2
2
  "$type": "page",
3
3
  "id": "meridian-error",
4
4
  "status": "published",
5
- "layout": "default",
6
5
  "metadata": {
7
6
  "title": "Something went wrong — Meridian",
8
7
  "description": "An unexpected error occurred while serving this page.",
9
- "sitemap": { "visible": false }
8
+ "sitemap": {
9
+ "visible": false
10
+ }
10
11
  },
11
12
  "regions": {
12
13
  "main": [
13
14
  {
15
+ "$type": "component",
14
16
  "id": "error-hero",
15
17
  "component": "home_hero",
16
18
  "props": {