@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
@@ -1,6 +1,7 @@
1
1
  // Declarative events (design/04): elements opt in via data-aci-event, with
2
2
  // data-aci-on=click|visible (visible = first intersection >= 50%, once per
3
- // element per page view) and data-aci-prop-* string properties. One
3
+ // element per page view) and data-aci-prop-* string properties. Every event
4
+ // carries the current page url so ingest can derive path and UTM. One
4
5
  // delegated click listener + one IntersectionObserver regardless of element
5
6
  // count. Malformed declarations are ignored silently.
6
7
  const EVENT_ATTR = "data-aci-event";
@@ -35,13 +36,13 @@ function extractProps(el) {
35
36
  }
36
37
  return props;
37
38
  }
38
- function declaredEvent(el, debug) {
39
+ function declaredEvent(el, url, debug) {
39
40
  const eventName = toCustomEventName(el.getAttribute(EVENT_ATTR));
40
41
  if (eventName === undefined) {
41
42
  debug?.("capture", { reason: "malformed_declaration", attr: EVENT_ATTR });
42
43
  return undefined;
43
44
  }
44
- return { eventName, properties: extractProps(el) };
45
+ return { eventName, url, properties: extractProps(el) };
45
46
  }
46
47
  function trigger(el) {
47
48
  return el.getAttribute(ON_ATTR)?.trim().toLowerCase() ?? "click";
@@ -53,6 +54,7 @@ function trigger(el) {
53
54
  */
54
55
  export function startDeclarative(emit, env, refresh) {
55
56
  const { document: doc, debug } = env;
57
+ const loc = doc.defaultView?.location;
56
58
  doc.addEventListener("click", (e) => {
57
59
  try {
58
60
  const target = e.target;
@@ -66,7 +68,7 @@ export function startDeclarative(emit, env, refresh) {
66
68
  return;
67
69
  if (trigger(el) !== "click")
68
70
  return; // visible or malformed trigger
69
- const event = declaredEvent(el, debug);
71
+ const event = declaredEvent(el, loc?.href, debug);
70
72
  if (event)
71
73
  emit(event);
72
74
  }
@@ -92,7 +94,7 @@ export function startDeclarative(emit, env, refresh) {
92
94
  continue;
93
95
  fired.add(entry.target);
94
96
  obs.unobserve(entry.target); // once per element per page view
95
- const event = declaredEvent(entry.target, debug);
97
+ const event = declaredEvent(entry.target, loc?.href, debug);
96
98
  if (event)
97
99
  emit(event);
98
100
  }
@@ -39,12 +39,21 @@ export interface RouteDeps {
39
39
  marker: () => boolean;
40
40
  gapBuffer: GapBuffer;
41
41
  batcher: EventSink;
42
+ /**
43
+ * Whether a CMP-preset site's consent decision is still pending and no
44
+ * identity exists (consenthold.ts). Absent means never pending.
45
+ */
46
+ consentPending?: () => boolean;
47
+ /** In-memory hold for consent-pending events (consenthold.ts). */
48
+ consentHold?: EventSink;
42
49
  }
43
50
  /**
44
- * routeEvent implements the emit routing (design/05): events captured in
45
- * the consent gap — envelope grants personalization AND the marker is
46
- * absent — are buffered; everything else goes straight to the batcher (the
47
- * collector enforces consent server-side).
51
+ * routeEvent implements the emit routing (design/05): events captured while
52
+ * a CMP's consent decision is pending (and no identity exists) are held in
53
+ * page memory until the decision; events captured in the consent gap —
54
+ * envelope grants personalization AND the marker is absent — are buffered;
55
+ * everything else goes straight to the batcher (the collector enforces
56
+ * consent server-side).
48
57
  */
49
58
  export declare function routeEvent(event: CollectEvent, deps: RouteDeps): void;
50
59
  /**
@@ -4,20 +4,23 @@
4
4
  // response. Bounded at 50 (drop-oldest), persisted per-tab in
5
5
  // sessionStorage["aci:buf"], purged on withdrawal, flushed through the
6
6
  // normal batcher when the marker appears (checked at init and on every
7
- // flush tick). Flushed events keep their eventIds.
7
+ // flush tick). Flushed events keep their eventIds. Storage is authoritative:
8
+ // a BFCache-retained document must observe another document's drain/purge.
8
9
  export const GAP_BUFFER_KEY = "aci:buf";
9
10
  export const GAP_BUFFER_CAP = 50;
10
11
  const MINT_MARKER_COOKIE = "aci_m";
11
12
  export class GapBuffer {
12
- events;
13
+ // Only retain events in memory when storage is unavailable. A successful
14
+ // storage operation must never leave a second, stale owner of that queue.
15
+ events = [];
13
16
  storage;
14
17
  debug;
15
18
  constructor(env) {
16
19
  this.debug = env?.debug;
17
20
  this.storage = env?.storage;
18
- this.events = this.restore();
19
21
  }
20
22
  push(event) {
23
+ this.events = this.restore();
21
24
  this.events.push(event);
22
25
  if (this.events.length > GAP_BUFFER_CAP) {
23
26
  this.events.splice(0, this.events.length - GAP_BUFFER_CAP);
@@ -27,13 +30,14 @@ export class GapBuffer {
27
30
  }
28
31
  /** drain returns and clears all buffered events, preserving order and ids. */
29
32
  drain() {
30
- const drained = this.events;
33
+ const drained = this.restore();
31
34
  this.events = [];
32
35
  this.persist();
33
36
  return drained;
34
37
  }
35
38
  /** purge empties the buffer (consent withdrawal). */
36
39
  purge() {
40
+ this.events = this.restore();
37
41
  if (this.events.length > 0) {
38
42
  this.debug?.("buffer", { reason: "purged", count: this.events.length });
39
43
  }
@@ -41,12 +45,12 @@ export class GapBuffer {
41
45
  this.persist();
42
46
  }
43
47
  size() {
44
- return this.events.length;
48
+ return this.restore().length;
45
49
  }
46
50
  restore() {
47
51
  const storage = this.storage;
48
52
  if (!storage)
49
- return [];
53
+ return this.events;
50
54
  let raw;
51
55
  try {
52
56
  raw = storage.getItem(GAP_BUFFER_KEY);
@@ -86,6 +90,7 @@ export class GapBuffer {
86
90
  else {
87
91
  storage.setItem(GAP_BUFFER_KEY, JSON.stringify(this.events));
88
92
  }
93
+ this.events = [];
89
94
  }
90
95
  catch (err) {
91
96
  this.storage = undefined;
@@ -104,12 +109,18 @@ export function hasMintMarker(doc) {
104
109
  return false;
105
110
  }
106
111
  /**
107
- * routeEvent implements the emit routing (design/05): events captured in
108
- * the consent gap — envelope grants personalization AND the marker is
109
- * absent — are buffered; everything else goes straight to the batcher (the
110
- * collector enforces consent server-side).
112
+ * routeEvent implements the emit routing (design/05): events captured while
113
+ * a CMP's consent decision is pending (and no identity exists) are held in
114
+ * page memory until the decision; events captured in the consent gap —
115
+ * envelope grants personalization AND the marker is absent — are buffered;
116
+ * everything else goes straight to the batcher (the collector enforces
117
+ * consent server-side).
111
118
  */
112
119
  export function routeEvent(event, deps) {
120
+ if (deps.consentHold && deps.consentPending?.()) {
121
+ deps.consentHold.push(event);
122
+ return;
123
+ }
113
124
  if (deps.grants() && !deps.marker()) {
114
125
  deps.gapBuffer.push(event);
115
126
  return;
@@ -3,9 +3,11 @@
3
3
  // init(); every failure is swallowed after a debug record so the page is
4
4
  // never affected (QR-2/3).
5
5
  import { installCommandQueue } from "./api.js";
6
- import { createAssignments, withAssignmentContext } from "./assignments.js";
6
+ import { createAssignments, readAssignments, withAssignmentContext } from "./assignments.js";
7
7
  import { Batcher } from "./batcher.js";
8
+ import { CONSENT_BANNER_DECIDED_EVENT, consentBannerPending, } from "../consent-banner/index.js";
8
9
  import { createDebug, readTagConfig } from "./config.js";
10
+ import { ConsentHold, resolveConsentHold } from "./consenthold.js";
9
11
  import { start as startC15t } from "./consent/adapters/c15t.js";
10
12
  import { start as startCookiebot } from "./consent/adapters/cookiebot.js";
11
13
  import { start as startOneTrust } from "./consent/adapters/onetrust.js";
@@ -53,6 +55,9 @@ export function init(win) {
53
55
  w.__aci_loaded = true;
54
56
  if (!hasBaseline(w))
55
57
  return;
58
+ // Snapshot before exposures or queued commands. Cookie expiry, hydration
59
+ // and BFCache never turn this synthetic document into a genuine visit.
60
+ const measurementDisabled = w.__aci_measurement_disabled === true;
56
61
  const cfg = readTagConfig(w.document.currentScript);
57
62
  const debug = createDebug(cfg.debug);
58
63
  try {
@@ -74,6 +79,33 @@ export function init(win) {
74
79
  return false;
75
80
  }
76
81
  };
82
+ const decided = () => {
83
+ try {
84
+ return readConsentCookie(doc) !== undefined;
85
+ }
86
+ catch {
87
+ return false;
88
+ }
89
+ };
90
+ // Consent-pending hold (personalization-onboarding REQ-15): a decision
91
+ // is pending when a consent integration has declared it will write the
92
+ // envelope and has not yet. A CMP preset is that declaration; under the
93
+ // manual preset it is ACI's own consent banner, through its state API.
94
+ // Until the decision, and while no identity exists, the collector could
95
+ // only drop what we send, so events wait in page memory instead. A
96
+ // manual site with no banner keeps immediate sending.
97
+ const integrationPending = cfg.preset === "manual"
98
+ ? () => {
99
+ try {
100
+ return consentBannerPending(doc);
101
+ }
102
+ catch {
103
+ return false;
104
+ }
105
+ }
106
+ : () => true;
107
+ const consentHold = new ConsentHold({ debug });
108
+ const consentPending = () => !decided() && !marker() && integrationPending();
77
109
  const transportEnv = {
78
110
  navigator: w.navigator,
79
111
  fetch: typeof w.fetch === "function"
@@ -85,9 +117,16 @@ export function init(win) {
85
117
  batcher = new Batcher({
86
118
  send: (body) => send(body, transportEnv),
87
119
  debug,
88
- // The marker is re-checked on every flush tick, so a mint by another
89
- // tab or navigation is noticed.
90
- onTick: () => maybeFlushGapBuffer(gapBuffer, batcher, marker, debug),
120
+ // Every flush tick re-checks the consent decision (so a decision
121
+ // written by another tab or site code releases or discards the hold)
122
+ // and then the marker (so a mint by another tab or navigation is
123
+ // noticed). Held events are older than gap-buffered ones: release first.
124
+ onTick: () => {
125
+ if (measurementDisabled)
126
+ return;
127
+ resolveConsentHold(consentHold, { decided, grants, batcher, debug });
128
+ maybeFlushGapBuffer(gapBuffer, batcher, marker, debug);
129
+ },
91
130
  timers: {
92
131
  setInterval: (fn, ms) => w.setInterval(fn, ms),
93
132
  clearInterval: (id) => w.clearInterval(id),
@@ -95,26 +134,44 @@ export function init(win) {
95
134
  documentTarget: doc,
96
135
  windowTarget: w,
97
136
  });
98
- // While an assignment is active, every emitted event carries the first
99
- // active assignment's experiment context (REQ-3) unless it already has
100
- // one — exposures and explicit conversions carry their own. The handle
137
+ // Every emitted event carries all active assignments (REQ-3) unless it
138
+ // already supplies its explicit context — exposures and explicit conversions carry their own. The handle
101
139
  // is created (without emitting) BEFORE emit so every dependency of emit
102
140
  // exists before emit can run; exposures flow at start() below.
103
141
  const assignments = createAssignments({ document: doc, window: w, debug });
104
142
  const emit = (event) => {
143
+ if (measurementDisabled)
144
+ return;
105
145
  routeEvent({
106
146
  ...withAssignmentContext(event, assignments),
107
147
  eventId: ulid(undefined, (bytes) => w.crypto.getRandomValues(bytes)),
108
- }, { grants, marker, gapBuffer, batcher });
148
+ }, { grants, marker, gapBuffer, batcher, consentHold, consentPending });
109
149
  };
150
+ // A decision written through the SDK (or announced by the ACI banner)
151
+ // flushes at once: the tick releases (grant) or discards (anything else)
152
+ // the hold, and a released first view leaves before the visitor can
153
+ // navigate away.
154
+ const onDecision = () => batcher.flush();
155
+ doc.addEventListener(CONSENT_BANNER_DECIDED_EVENT, onDecision);
110
156
  // Started BEFORE the command-queue drain and page views: pre-load
111
157
  // aci('track', ...) calls and the initial rum.page_view must be stamped
112
158
  // with the active assignment context, or queued conversions can never
113
159
  // match a goal (review-quorum finding). Consent commands drained after
114
160
  // this stay safe — events wait in the gap buffer until grants resolve.
115
161
  assignments.start(emit);
116
- installCommandQueue(w, cfg, {
117
- track: (name, properties) => emit({ eventName: name, properties }),
162
+ let emitPageView = () => { };
163
+ const navigation = installNavigationHook(w, [
164
+ contexts => assignments.rearm(contexts),
165
+ () => emitPageView(),
166
+ ], () => assignments.clear());
167
+ // Astro dispatches this nonbubbling event on document after the router commits.
168
+ doc.addEventListener("astro:page-load", () => navigation.complete({
169
+ url: w.location.href,
170
+ assignments: readAssignments(doc, debug),
171
+ }));
172
+ installCommandQueue(w, {
173
+ navigationComplete: completion => navigation.complete(completion),
174
+ track: (name, properties) => emit({ eventName: name, properties, url: w.location.href }),
118
175
  consent: ({ p, src }) => {
119
176
  const purposes = sanitizePurposes(p);
120
177
  if (purposes === undefined) {
@@ -126,25 +183,19 @@ export function init(win) {
126
183
  debug("consent", { reason: "malformed_source" });
127
184
  return;
128
185
  }
129
- if (!applyConsent(purposes, source, { document: doc, gapBuffer })) {
186
+ if (!applyConsent(purposes, source, { document: doc, gapBuffer, onDecision })) {
130
187
  debug("consent", { reason: "oversized_envelope" });
131
188
  }
132
189
  },
133
190
  }, debug);
134
- const emitPageView = startPageViews(emit, w);
135
- // The single history patch (one pushState/replaceState wrap, one dedupe
136
- // state, one popstate listener). Order is load-bearing: the assignments
137
- // re-arm runs BEFORE the SPA page view so rum.page_view is stamped with
138
- // the new page's experiment context.
139
- installNavigationHook(w, [
140
- () => assignments.rearm(),
141
- emitPageView,
142
- ]);
191
+ emitPageView = startPageViews(emit, w);
143
192
  startDeclarative(emit, {
144
193
  document: doc,
145
194
  intersectionObserver: w.IntersectionObserver,
146
195
  debug,
147
196
  }, () => {
197
+ if (measurementDisabled)
198
+ return;
148
199
  void refreshClaims(transportEnv).then((refreshed) => {
149
200
  if (refreshed)
150
201
  w.location.reload();
@@ -169,6 +220,7 @@ export function init(win) {
169
220
  document: doc,
170
221
  categories: cfg.categories,
171
222
  gapBuffer,
223
+ onDecision,
172
224
  debug,
173
225
  scheduler: {
174
226
  setTimeout: (fn, ms) => w.setTimeout(fn, ms),
@@ -177,7 +229,12 @@ export function init(win) {
177
229
  });
178
230
  }
179
231
  batcher.start();
180
- maybeFlushGapBuffer(gapBuffer, batcher, marker, debug);
232
+ // Registered after batcher.start(), so the batcher's own pagehide flush
233
+ // has already released a hold whose decision exists; whatever remains is
234
+ // undecided and never leaves the page.
235
+ w.addEventListener("pagehide", () => consentHold.discard("unload"));
236
+ if (!measurementDisabled)
237
+ maybeFlushGapBuffer(gapBuffer, batcher, marker, debug);
181
238
  debug("init", { config: cfg });
182
239
  }
183
240
  catch (err) {
@@ -1,3 +1,4 @@
1
+ import type { ExperimentAssignment } from "./assignments.js";
1
2
  import type { PendingEvent } from "./batcher.js";
2
3
  export declare const PAGE_VIEW_EVENT = "rum.page_view";
3
4
  interface HistoryLike {
@@ -14,17 +15,16 @@ export interface PageViewWindow {
14
15
  history: HistoryLike;
15
16
  addEventListener(type: string, fn: () => void): void;
16
17
  }
17
- /**
18
- * installNavigationHook wires pushState/replaceState/popstate and invokes
19
- * the subscribers, in order, once per SPA route change; hash-only changes
20
- * and same-URL repeats never fire. Installed exactly ONCE by init — one
21
- * history patch, one dedupe state — and shared by the assignments collector
22
- * and page views so "page view" means exactly the same thing to both
23
- * (REQ-2). Subscriber order is the contract: init lists the assignments
24
- * re-arm BEFORE the page-view emitter so rum.page_view is stamped with the
25
- * new page's experiment context.
26
- */
27
- export declare function installNavigationHook(win: PageViewWindow, subscribers: ReadonlyArray<() => void>): void;
18
+ export interface NavigationCompletion {
19
+ /** Absolute URL of the page the framework just committed. */
20
+ url: string;
21
+ /** Complete committed assignment set, including [] for a base page. */
22
+ assignments: ExperimentAssignment[];
23
+ }
24
+ /** History signals invalidate context; only framework completion arms a view. */
25
+ export declare function installNavigationHook(win: PageViewWindow, subscribers: ReadonlyArray<(assignments: ExperimentAssignment[]) => void>, onStart?: () => void): {
26
+ complete(completion: NavigationCompletion): void;
27
+ };
28
28
  /**
29
29
  * startPageViews emits the initial page view and returns the emitter for
30
30
  * SPA route-change views — init subscribes it to the single navigation hook
@@ -1,45 +1,50 @@
1
1
  // Page views (design/04): eventName "rum.page_view" with url (the full href
2
2
  // including query) and referrer — the SDK never populates utm, ingest
3
3
  // derives it from the URL. One event per document load plus one per
4
- // history-API route change; hash-only changes and same-URL repeats never
4
+ // completed framework route change; hash-only changes and same-URL repeats never
5
5
  // double count.
6
6
  export const PAGE_VIEW_EVENT = "rum.page_view";
7
7
  function stripHash(href) {
8
8
  const i = href.indexOf("#");
9
9
  return i === -1 ? href : href.slice(0, i);
10
10
  }
11
- /**
12
- * installNavigationHook wires pushState/replaceState/popstate and invokes
13
- * the subscribers, in order, once per SPA route change; hash-only changes
14
- * and same-URL repeats never fire. Installed exactly ONCE by init — one
15
- * history patch, one dedupe state — and shared by the assignments collector
16
- * and page views so "page view" means exactly the same thing to both
17
- * (REQ-2). Subscriber order is the contract: init lists the assignments
18
- * re-arm BEFORE the page-view emitter so rum.page_view is stamped with the
19
- * new page's experiment context.
20
- */
21
- export function installNavigationHook(win, subscribers) {
22
- let last = stripHash(win.location.href);
11
+ /** History signals invalidate context; only framework completion arms a view. */
12
+ export function installNavigationHook(win, subscribers, onStart = () => { }) {
13
+ let observed = stripHash(win.location.href);
14
+ let completed = observed;
15
+ let pending = false;
23
16
  const handler = () => {
24
17
  const current = stripHash(win.location.href);
25
- if (current === last)
26
- return; // hash-only change or same-URL repeat
27
- last = current;
28
- for (const onNavigate of subscribers)
29
- onNavigate();
18
+ if (current === observed)
19
+ return;
20
+ observed = current;
21
+ pending = true;
22
+ onStart();
30
23
  };
31
24
  const history = win.history;
32
- const wrap = (method) => {
25
+ for (const method of ["pushState", "replaceState"]) {
33
26
  const original = history[method].bind(history);
34
27
  history[method] = (...args) => {
35
28
  const result = original(...args);
36
29
  handler();
37
30
  return result;
38
31
  };
39
- };
40
- wrap("pushState");
41
- wrap("replaceState");
32
+ }
42
33
  win.addEventListener("popstate", handler);
34
+ return {
35
+ complete({ url, assignments }) {
36
+ const current = stripHash(win.location.href);
37
+ if (stripHash(url) !== current)
38
+ return; // late commit for another route
39
+ handler();
40
+ if (!pending && completed === current)
41
+ return;
42
+ pending = false;
43
+ completed = current;
44
+ for (const onNavigate of subscribers)
45
+ onNavigate(assignments);
46
+ },
47
+ };
43
48
  }
44
49
  /**
45
50
  * startPageViews emits the initial page view and returns the emitter for
@@ -16,6 +16,10 @@ export interface ConsentBannerOptions {
16
16
  /** Callback fired after a consent decision is written. */
17
17
  onDecision?: (granted: boolean) => void;
18
18
  }
19
+ /** Rendered with the page by ConsentBanner.astro, before any script runs. */
20
+ export declare const CONSENT_BANNER_MOUNT_ATTR = "data-aci-consent-banner-mount";
21
+ /** Dispatched on the document right after the banner writes a decision. */
22
+ export declare const CONSENT_BANNER_DECIDED_EVENT = "aci:consent-banner-decided";
19
23
  /**
20
24
  * hasConsentCookie returns true when the aci_consent cookie exists (the user
21
25
  * has already made a decision, regardless of whether they granted or denied).
@@ -23,6 +27,19 @@ export interface ConsentBannerOptions {
23
27
  export declare function hasConsentCookie(doc: {
24
28
  cookie: string;
25
29
  }): boolean;
30
+ /**
31
+ * consentBannerPending is the banner's state API for the collect SDK
32
+ * (personalization-onboarding REQ-15): true while this page's ACI consent
33
+ * banner awaits the visitor's decision, meaning the banner is declared (its
34
+ * mount point, which ConsentBanner.astro renders with the HTML) or mounted,
35
+ * and no aci_consent decision exists. Because the mount point ships in the
36
+ * HTML, the answer is already right for the first deferred script to run,
37
+ * whether the collect SDK or the banner's own script loads first.
38
+ */
39
+ export declare function consentBannerPending(doc: {
40
+ cookie: string;
41
+ querySelector(selectors: string): unknown;
42
+ }): boolean;
26
43
  /**
27
44
  * mountConsentBanner checks for an existing consent cookie. If none exists,
28
45
  * it injects a banner into the document body and wires up accept/reject
@@ -18,6 +18,10 @@ const DEFAULTS = {
18
18
  const BANNER_ID = "aci-consent-banner";
19
19
  const BANNER_ATTR = "data-aci-consent-banner";
20
20
  const BANNER_HIDDEN = "aci-consent-banner--hidden";
21
+ /** Rendered with the page by ConsentBanner.astro, before any script runs. */
22
+ export const CONSENT_BANNER_MOUNT_ATTR = "data-aci-consent-banner-mount";
23
+ /** Dispatched on the document right after the banner writes a decision. */
24
+ export const CONSENT_BANNER_DECIDED_EVENT = "aci:consent-banner-decided";
21
25
  /**
22
26
  * hasConsentCookie returns true when the aci_consent cookie exists (the user
23
27
  * has already made a decision, regardless of whether they granted or denied).
@@ -25,6 +29,20 @@ const BANNER_HIDDEN = "aci-consent-banner--hidden";
25
29
  export function hasConsentCookie(doc) {
26
30
  return readConsentCookie(doc) !== undefined;
27
31
  }
32
+ /**
33
+ * consentBannerPending is the banner's state API for the collect SDK
34
+ * (personalization-onboarding REQ-15): true while this page's ACI consent
35
+ * banner awaits the visitor's decision, meaning the banner is declared (its
36
+ * mount point, which ConsentBanner.astro renders with the HTML) or mounted,
37
+ * and no aci_consent decision exists. Because the mount point ships in the
38
+ * HTML, the answer is already right for the first deferred script to run,
39
+ * whether the collect SDK or the banner's own script loads first.
40
+ */
41
+ export function consentBannerPending(doc) {
42
+ if (hasConsentCookie(doc))
43
+ return false;
44
+ return doc.querySelector(`[${CONSENT_BANNER_MOUNT_ATTR}], [${BANNER_ATTR}]`) !== null;
45
+ }
28
46
  /**
29
47
  * mountConsentBanner checks for an existing consent cookie. If none exists,
30
48
  * it injects a banner into the document body and wires up accept/reject
@@ -47,6 +65,11 @@ export function mountConsentBanner(doc, opts = {}) {
47
65
  const purposes = granted ? [PERSONALIZATION_PURPOSE] : [];
48
66
  if (!applyConsent(purposes, config.source, { document: doc }))
49
67
  return;
68
+ // Tell a collect SDK on this page to act on the decision now rather than
69
+ // at its next flush tick (it releases or discards what it held).
70
+ const EventCtor = doc.defaultView?.Event;
71
+ if (EventCtor)
72
+ doc.dispatchEvent(new EventCtor(CONSENT_BANNER_DECIDED_EVENT));
50
73
  banner.classList.add(BANNER_HIDDEN);
51
74
  setTimeout(cleanup, 300);
52
75
  opts.onDecision?.(granted);
@@ -3,6 +3,7 @@ export * from './routes.js';
3
3
  export * from './types.js';
4
4
  export * from './personalization.js';
5
5
  export * from './cache.js';
6
- export * from './resolve-slots.js';
6
+ export { FragmentResolutionError, MAX_EXPANDED_BLOCKS, MAX_FRAGMENT_DEPTH, fragmentToBlock, preserveFragmentScope, resolveFragmentNodes, resolveSlots, resolveSlotsSync, } from './resolve-slots.js';
7
+ export type { FragmentBlock, FragmentData, FragmentLoader } from './resolve-slots.js';
7
8
  export * from './default-error-page.js';
8
9
  export * from './error-pages.js';
@@ -3,6 +3,9 @@ export * from './routes.js';
3
3
  export * from './types.js';
4
4
  export * from './personalization.js';
5
5
  export * from './cache.js';
6
- export * from './resolve-slots.js';
6
+ // Explicit list, not `export *`: fragmentRootScope is runtime-only provenance
7
+ // shared with mintlify/blocks.ts via a direct resolve-slots import and must
8
+ // never leak into the public '@gradial/aci/content' surface.
9
+ export { FragmentResolutionError, MAX_EXPANDED_BLOCKS, MAX_FRAGMENT_DEPTH, fragmentToBlock, preserveFragmentScope, resolveFragmentNodes, resolveSlots, resolveSlotsSync, } from './resolve-slots.js';
7
10
  export * from './default-error-page.js';
8
11
  export * from './error-pages.js';
@@ -2,6 +2,7 @@ import type { KernelPage, KernelRouteMetadata, KernelSiteConfig, RenderInput } f
2
2
  export interface RouteEntry {
3
3
  slug: string;
4
4
  path: string;
5
+ variantKey: string;
5
6
  payloadRef: string;
6
7
  digest: string;
7
8
  fragmentRefs?: string[];
@@ -19,8 +20,21 @@ export interface FragmentEntry {
19
20
  digest: string;
20
21
  fragmentRefs?: string[];
21
22
  }
23
+ export interface LayoutVariantEntry {
24
+ payloadRef: string;
25
+ digest: string;
26
+ slots: LayoutSlot[];
27
+ defaults?: Record<string, FragmentRef[]>;
28
+ }
29
+ /**
30
+ * Derived template render plan in the compiled release wire format.
31
+ * This is never an authorable layout: page templates own regions and defaults.
32
+ * The LayoutEntry name and layouts wire key remain for retained release readers.
33
+ */
22
34
  export interface LayoutEntry {
23
35
  name: string;
36
+ digest?: string;
37
+ variants?: Record<string, LayoutVariantEntry>;
24
38
  payloadRef: string;
25
39
  slots: LayoutSlot[];
26
40
  defaults?: Record<string, FragmentRef[]>;
@@ -32,7 +46,6 @@ export interface LayoutSlot {
32
46
  export interface FragmentRef {
33
47
  kind: string;
34
48
  fragmentId: string;
35
- inline: boolean;
36
49
  }
37
50
  export interface CompiledManifest {
38
51
  manifestVersion: string;
@@ -59,8 +72,9 @@ export declare class LayoutNotFoundError extends Error {
59
72
  constructor(name: string, cause?: unknown);
60
73
  }
61
74
  export interface RenderInputOptions {
75
+ variantKey: string;
62
76
  domain?: string;
63
77
  locale?: string;
64
78
  }
65
- export declare function loadRenderInput(provider: ContentProvider, route?: string, options?: RenderInputOptions): Promise<RenderInput>;
79
+ export declare function loadRenderInput(provider: ContentProvider, route: string | undefined, options: RenderInputOptions): Promise<RenderInput>;
66
80
  export declare function routeMetadataForContent(siteConfig: KernelSiteConfig, page: KernelPage | null): KernelRouteMetadata;
@@ -24,8 +24,11 @@ export class LayoutNotFoundError extends Error {
24
24
  }
25
25
  }
26
26
  import { getCachedSiteConfig } from './cache.js';
27
- export async function loadRenderInput(provider, route = '/', options = {}) {
27
+ export async function loadRenderInput(provider, route = '/', options) {
28
28
  const normalized = normalizeRoute(route);
29
+ if (typeof options?.variantKey !== 'string' || !options.variantKey.trim() || options.variantKey !== options.variantKey.trim()) {
30
+ throw new Error(`ACI render variant identity is missing or invalid for route ${normalized}; supply an explicit variantKey`);
31
+ }
29
32
  const [siteConfig, page] = await Promise.all([
30
33
  getCachedSiteConfig(provider),
31
34
  provider.getPage(normalized).catch((error) => {
@@ -36,6 +39,7 @@ export async function loadRenderInput(provider, route = '/', options = {}) {
36
39
  ]);
37
40
  return {
38
41
  route: normalized,
42
+ variantKey: options.variantKey,
39
43
  domain: options.domain || siteConfig.domain,
40
44
  locale: (options.locale || siteConfig.defaultLocale || 'en-us').toLowerCase(),
41
45
  siteConfig,
@@ -1,5 +1,5 @@
1
1
  import type { LayoutEntry } from './provider.js';
2
- import type { KernelPageBlock } from './types.js';
2
+ import type { KernelPageBlock, KernelPageNode } from './types.js';
3
3
  export interface FragmentBlock {
4
4
  $type: 'component';
5
5
  component: string;
@@ -13,4 +13,26 @@ export interface FragmentData {
13
13
  props?: Record<string, unknown>;
14
14
  }
15
15
  export type FragmentLoader = (id: string) => Promise<FragmentData>;
16
- export declare function resolveSlots(layout: LayoutEntry, pageRegions: Record<string, KernelPageBlock[]>, getFragment: FragmentLoader): Promise<Record<string, KernelPageBlock[]>>;
16
+ /**
17
+ * Shared Go/TS limit contract (sdk I-21). These values are pinned against
18
+ * internal/domain/content/compiler/testdata/limit-conformance.v1.json by
19
+ * limit-conformance.test.ts (TS) and limit_conformance_test.go (Go); the
20
+ * compiler's MaxFragmentDepth/MaxExpandedBlocks must match.
21
+ */
22
+ /** Bounds a selected fragment reference chain, counting its root fragment. */
23
+ export declare const MAX_FRAGMENT_DEPTH = 10;
24
+ /** Bounds rendered component occurrences per page, including repeated placements. */
25
+ export declare const MAX_EXPANDED_BLOCKS = 4096;
26
+ /** Failures in a selected fragment are never eligible for local-content fallback. */
27
+ export declare class FragmentResolutionError extends Error {
28
+ constructor(message: string, options?: ErrorOptions);
29
+ }
30
+ export declare const fragmentRootScope: unique symbol;
31
+ export declare function preserveFragmentScope<T extends object>(source: object, target: T): T;
32
+ export declare function resolveSlots(plan: LayoutEntry, pageRegions: Record<string, KernelPageNode[]>, getFragment: FragmentLoader): Promise<Record<string, KernelPageBlock[]>>;
33
+ /** The config-time error seat uses the identical traversal with synchronous file reads. */
34
+ export declare function resolveSlotsSync(plan: LayoutEntry, pageRegions: Record<string, KernelPageNode[]>, getFragment: (id: string) => FragmentData): Record<string, KernelPageBlock[]>;
35
+ /** Resolve compiled references through the exact fragment bindings selected for this page. */
36
+ export declare function resolveFragmentNodes(nodes: readonly KernelPageNode[], getFragment: FragmentLoader): Promise<KernelPageBlock[]>;
37
+ /** Use the same fragment root identity for layout defaults and region placements. */
38
+ export declare function fragmentToBlock(fragment: FragmentData, id: string): KernelPageBlock;