@gradial/aci 0.1.53-canary.693 → 0.1.53-canary.700

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 (74) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +4 -4
  3. package/dist/astro/index.js +2 -5
  4. package/dist/cli/compile-registry.mjs +8 -0
  5. package/dist/content/component-block.d.ts +17 -0
  6. package/dist/content/component-block.js +40 -0
  7. package/dist/content/provider.d.ts +7 -1
  8. package/dist/content/provider.js +27 -0
  9. package/dist/content/resolve-slots.js +2 -4
  10. package/dist/content/types.d.ts +11 -0
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.js +1 -0
  13. package/dist/internal/aci-root.d.ts +1 -0
  14. package/dist/internal/aci-root.js +49 -0
  15. package/dist/next/error-seat.js +3 -4
  16. package/dist/next/page.js +1 -0
  17. package/dist/next/render.js +2 -8
  18. package/dist/platform-tests/adapters/astro.d.ts +2 -0
  19. package/dist/platform-tests/adapters/astro.js +101 -0
  20. package/dist/platform-tests/adapters/next.d.ts +9 -0
  21. package/dist/platform-tests/adapters/next.js +112 -0
  22. package/dist/platform-tests/adapters/site.d.ts +29 -0
  23. package/dist/platform-tests/adapters/site.js +92 -0
  24. package/dist/platform-tests/adapters/types.d.ts +27 -0
  25. package/dist/platform-tests/adapters/types.js +1 -0
  26. package/dist/platform-tests/index.d.ts +47 -0
  27. package/dist/platform-tests/index.js +50 -0
  28. package/dist/platform-tests/sample-props.d.ts +2 -0
  29. package/dist/platform-tests/sample-props.js +82 -0
  30. package/dist/platform-tests/site-check.d.ts +51 -0
  31. package/dist/platform-tests/site-check.js +193 -0
  32. package/dist/registry.d.ts +1 -1
  33. package/dist/testing/conformance/index.d.ts +2 -1
  34. package/dist/testing/conformance/index.js +2 -1
  35. package/dist/testing/conformance/next-stubs.d.ts +13 -4
  36. package/dist/testing/conformance/next-stubs.js +76 -4
  37. package/dist/testing/conformance/preset.js +1 -2
  38. package/dist/testing/conformance/root-identity.d.ts +0 -29
  39. package/dist/testing/conformance/root-identity.js +36 -344
  40. package/dist/testing/conformance/types.d.ts +1 -1
  41. package/dist/types/component.d.ts +11 -3
  42. package/dist/types/component.js +10 -1
  43. package/migrations/page-metadata-head.md +38 -0
  44. package/package.json +12 -8
  45. package/src/astro/Head.astro +18 -0
  46. package/src/cli/compile-registry.mjs +8 -0
  47. package/src/types/component.test.ts +17 -0
  48. package/src/types/component.ts +18 -3
  49. package/templates/astro/starter-content/src/components/chrome/Footer.astro +3 -4
  50. package/templates/astro/starter-content/src/components/chrome/Header.astro +3 -4
  51. package/templates/astro/starter-content/src/components/elements/Button.astro +4 -4
  52. package/templates/astro/starter-content/src/components/elements/ButtonBlock.astro +3 -4
  53. package/templates/astro/starter-content/src/components/sections/ContainerSection.astro +4 -4
  54. package/templates/astro/starter-content/src/components/sections/FeatureCard.astro +4 -4
  55. package/templates/astro/starter-content/src/components/sections/FeatureGrid.astro +4 -4
  56. package/templates/astro/starter-content/src/components/sections/HomeHero.astro +3 -4
  57. package/templates/astro/starter-content/src/components/sections/PersonalizationChoice.astro +4 -4
  58. package/templates/astro/starter-content/src/components/sections/StatsBar.astro +4 -4
  59. package/templates/astro/template/src/layouts/SiteLayout.astro +5 -11
  60. package/templates/astro/template/src/pages/404.astro +1 -4
  61. package/templates/astro/template/src/pages/500.astro +1 -4
  62. package/templates/astro/template/src/pages/[...slug].astro +1 -4
  63. package/templates/common/.agents/skills/authoring/SKILL.md +40 -4
  64. package/templates/common/.agents/skills/authoring/references/block-registry.md +42 -7
  65. package/templates/common/.agents/skills/authoring/references/sdk-migrations.md +87 -0
  66. package/templates/nextjs/starter-content/src/components/chrome/Footer.tsx +5 -4
  67. package/templates/nextjs/starter-content/src/components/chrome/Header.tsx +5 -4
  68. package/templates/nextjs/starter-content/src/components/elements/ButtonBlock.tsx +5 -4
  69. package/templates/nextjs/starter-content/src/components/sections/ContainerSection.tsx +9 -9
  70. package/templates/nextjs/starter-content/src/components/sections/FeatureCard.tsx +5 -4
  71. package/templates/nextjs/starter-content/src/components/sections/FeatureGrid.tsx +9 -9
  72. package/templates/nextjs/starter-content/src/components/sections/HomeHero.tsx +5 -4
  73. package/templates/nextjs/starter-content/src/components/sections/PersonalizationChoice.tsx +10 -10
  74. package/templates/nextjs/starter-content/src/components/sections/StatsBar.tsx +5 -4
package/CHANGELOG.md CHANGED
@@ -2,8 +2,87 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ### Added
6
+
7
+ - `AciRootProps` (the new name of `RootIdentityProps`, which stays as an
8
+ alias) and `aciRootAttrs(props)`, exported from `@gradial/aci`.
9
+ - The `rootIdentity` check covers every file that calls `createRegistry(`,
10
+ renders a component no content places with props derived from its
11
+ contract's schema, and prints its coverage: components checked, and each
12
+ one not run with the reason.
13
+ - Page `metadata.robots` accepts boolean `index` and `follow` directives.
14
+ Next and Astro emit the corresponding HTML tag. Publish excludes pages with
15
+ `index: false` from the release sitemap.
16
+
17
+ ### Migration required
18
+
19
+ - **Block root identity: render the root on every render** (ADR 0051) — each
20
+ registered component must put `aciRootAttrs(props)` (or its rest props) on
21
+ its own root element, and must render that root even when its content is
22
+ empty; the `rootIdentity` conformance check now fails a component that
23
+ renders nothing. Runbook: `.agents/skills/authoring/references/sdk-migrations.md`,
24
+ section "Root identity (0.1.52-rc.24)".
25
+ - **Migration required: test loader** — the `rootIdentity` check in
26
+ `@gradial/aci/testing` imports your component registry, so your test
27
+ process must be able to load TypeScript, JSX, and your `tsconfig` path
28
+ aliases. Run your test suite with the `tsx` loader, as the starters do:
29
+
30
+ ```json
31
+ "test": "node --import tsx --test tests/**/*.test.ts"
32
+ ```
33
+
34
+ A test process without the loader (for example `node --test` or
35
+ `node --test --experimental-strip-types`) now fails `npm test` with:
36
+
37
+ ```text
38
+ rootIdentity: cannot load src/cms/registry.ts: this test process cannot resolve the path alias "@/cms".
39
+ Run tests with: node --import tsx --test
40
+ See the @gradial/aci CHANGELOG, "Migration required: test loader".
41
+ ```
42
+
43
+ If you turned the check off with `checks: { rootIdentity: false }` because
44
+ it could not load CSS modules or path aliases, remove that option: the
45
+ check now loads stylesheets, images, fonts, `next/image`, `next/link`, and
46
+ `next/font` as inert stubs, and reads every registry file in the site.
47
+
48
+ There is no build gate. `next build` and `astro build` do not run the
49
+ check. The local signal is `npm test`, where the conformance check renders
50
+ every registered component in isolation. A component it cannot load or
51
+ render is reported as not run, never as a failure.
52
+ - Existing Astro sites must install `@gradial/aci/astro/Head` and pass resolved
53
+ metadata as one object through their route and layout. Next sites must upgrade
54
+ the SDK and rebuild. See [page metadata migration](./migrations/page-metadata-head.md).
55
+ Packed starter HTML checks in `make test-frontend-contract` verify the tags.
56
+ Sitemap exclusion can precede an SDK upgrade. Inspect the rendered preview
57
+ before telling an author that `noindex` is present. No platform test currently
58
+ enforces that check on older site builds.
59
+
60
+ ### Removed
61
+
62
+ - `astroAuthoredBlocks` is no longer exported from `@gradial/aci/testing`:
63
+ the Astro `rootIdentity` check renders components instead of scanning
64
+ `dist/`. `findRootIdentityFailures` and `RootIdentityFailure` are no longer
65
+ exported either; `checkRootIdentity` and `formatRootIdentityFailures` stay.
66
+
5
67
  ### Changed
6
68
 
69
+ - **`rootIdentity` reads every registry file again.** The `registry` option
70
+ introduced in 0.1.52-rc.33 is still accepted, but it is no longer required:
71
+ the check covers every file that calls `createRegistry(` (a main registry
72
+ and an error-page registry are both checked). Sites that added the option
73
+ for rc.33 need no change.
74
+ - **Nested blocks carry their root identity in their props.** The renderers
75
+ now put `data-aci-block` and `data-aci-component` into the `props` of
76
+ every component block nested in a block's props, at any depth (a block
77
+ slot, or a list inside an item such as `tabs[].blocks`). A container that
78
+ renders child blocks from plain data with `<Component {...block.props} />`
79
+ now emits each child's identity on the child's root, so `rootIdentity`
80
+ passes for it without a site change. Containers that call
81
+ `renderChildren` are unchanged: the same values are applied once. A
82
+ container that rebuilds a child's props from selected fields (for example
83
+ `<Component text={block.props.text} />`) still drops the identity and is
84
+ still reported. The `props` of nested blocks now contain the two identity
85
+ keys at render time; the compiled content does not change.
7
86
  - Immutable code builds (`ACI_CODE_ARTIFACT_BUILD=1`) no longer read or compile
8
87
  authored error-page content. Native client error seats use the shipped
9
88
  default; publish resolves authored CDN error pages independently. Error
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agentic Content Infrastructure SDK
2
2
 
3
- `@gradial/aci` `0.1.53-canary.693` is a pre-1.0 public-surface reset for the ACI
3
+ `@gradial/aci` `0.1.53-canary.700` is a pre-1.0 public-surface reset for the ACI
4
4
  client SDK. Breaking changes from the old `0.1.x` accidental surface are
5
5
  intentional: the root import is framework-neutral, framework runtime behavior is
6
6
  behind explicit subpaths, and the first required customer path is React/Next.
@@ -8,7 +8,7 @@ behind explicit subpaths, and the first required customer path is React/Next.
8
8
  ## Install
9
9
 
10
10
  ```bash
11
- npm install @gradial/aci@0.1.53-canary.693 zod@^4
11
+ npm install @gradial/aci@0.1.53-canary.700 zod@^4
12
12
  ```
13
13
 
14
14
  ## Root Import
@@ -137,13 +137,13 @@ host bundles select and serve immutable release artifacts.
137
137
  The first RC scaffold path is Next:
138
138
 
139
139
  ```bash
140
- npx @gradial/aci@0.1.53-canary.693 init my-site --template next-starter
140
+ npx @gradial/aci@0.1.53-canary.700 init my-site --template next-starter
141
141
  ```
142
142
 
143
143
  List available templates:
144
144
 
145
145
  ```bash
146
- npx @gradial/aci@0.1.53-canary.693 init --list
146
+ npx @gradial/aci@0.1.53-canary.700 init --list
147
147
  ```
148
148
 
149
149
  ## Personalization: The One-Tag Install
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { contentWatchPlugin, damAssetPlugin, devRefreshPort, } from '../dev/content-watch.js';
4
4
  import { createDevRefreshBrowserScript } from '../dev/index.js';
5
+ import { withAciRootProps, withNestedAciRootProps } from '../internal/aci-root.js';
5
6
  // ---------------------------------------------------------------------------
6
7
  // Static paths helper — for Astro SSG
7
8
  // ---------------------------------------------------------------------------
@@ -57,14 +58,10 @@ export function createRenderChildren(registry) {
57
58
  return blocks.map((block) => {
58
59
  const entry = registry.find((r) => r.id === block.component);
59
60
  const hasBlockSlots = !!entry?.contract.blockSlots;
60
- const props = { ...block.props };
61
+ const props = withAciRootProps(withNestedAciRootProps(block.props), block);
61
62
  if (hasBlockSlots) {
62
63
  props.renderChildren = renderChildren;
63
64
  }
64
- // sdk I-19 (ADR 0051): root identity props are applied last so content
65
- // props cannot overwrite them. The template spreads them onto the root.
66
- props['data-aci-block'] = block.id;
67
- props['data-aci-component'] = block.component;
68
65
  return { Component: entry?.component, props };
69
66
  });
70
67
  }
@@ -503,6 +503,14 @@ async function generatePageSchema(schemaDir, componentEntries, outDir) {
503
503
  title: { type: 'string', minLength: 1 },
504
504
  description: { type: 'string' },
505
505
  canonical: { type: 'string' },
506
+ robots: {
507
+ type: 'object',
508
+ additionalProperties: false,
509
+ properties: {
510
+ index: { type: 'boolean' },
511
+ follow: { type: 'boolean' },
512
+ },
513
+ },
506
514
  },
507
515
  },
508
516
  regions: {
@@ -0,0 +1,17 @@
1
+ export interface ComponentBlock {
2
+ id: string;
3
+ component: string;
4
+ }
5
+ /**
6
+ * The content compiler's block predicate (`compiler.IsComponentBlock`): an
7
+ * explicit `$type: "component"` marker, or a non-empty string `component`
8
+ * with an object `props`. The shared Go/TS vector
9
+ * `internal/domain/content/compiler/testdata/component-block-predicate.v1.json`
10
+ * pins both sides.
11
+ */
12
+ export declare function isComponentBlock(node: Record<string, unknown>): boolean;
13
+ /**
14
+ * Every component block in a compiled page payload that carries an id,
15
+ * parents before children, nested slots included.
16
+ */
17
+ export declare function collectComponentBlocks(payload: unknown): ComponentBlock[];
@@ -0,0 +1,40 @@
1
+ // The content compiler's component-block rule, shared by rendering (root
2
+ // identity, slot resolution) and the rootIdentity conformance check.
3
+ /**
4
+ * The content compiler's block predicate (`compiler.IsComponentBlock`): an
5
+ * explicit `$type: "component"` marker, or a non-empty string `component`
6
+ * with an object `props`. The shared Go/TS vector
7
+ * `internal/domain/content/compiler/testdata/component-block-predicate.v1.json`
8
+ * pins both sides.
9
+ */
10
+ export function isComponentBlock(node) {
11
+ if (node.$type === 'component')
12
+ return true;
13
+ const props = node.props;
14
+ return typeof node.component === 'string' && node.component !== ''
15
+ && typeof props === 'object' && props !== null && !Array.isArray(props);
16
+ }
17
+ /**
18
+ * Every component block in a compiled page payload that carries an id,
19
+ * parents before children, nested slots included.
20
+ */
21
+ export function collectComponentBlocks(payload) {
22
+ const out = [];
23
+ const walk = (node) => {
24
+ if (Array.isArray(node)) {
25
+ for (const item of node)
26
+ walk(item);
27
+ return;
28
+ }
29
+ if (typeof node !== 'object' || node === null)
30
+ return;
31
+ const record = node;
32
+ if (isComponentBlock(record) && typeof record.id === 'string' && record.id !== '') {
33
+ out.push({ id: record.id, component: typeof record.component === 'string' ? record.component : '' });
34
+ }
35
+ for (const value of Object.values(record))
36
+ walk(value);
37
+ };
38
+ walk(payload);
39
+ return out;
40
+ }
@@ -1,4 +1,4 @@
1
- import type { KernelPage, KernelRouteMetadata, KernelSiteConfig, RenderInput } from './types.js';
1
+ import type { KernelPage, KernelPageRobots, KernelRouteMetadata, KernelSiteConfig, RenderInput } from './types.js';
2
2
  export interface RouteEntry {
3
3
  slug: string;
4
4
  path: string;
@@ -78,3 +78,9 @@ export interface RenderInputOptions {
78
78
  }
79
79
  export declare function loadRenderInput(provider: ContentProvider, route: string | undefined, options: RenderInputOptions): Promise<RenderInput>;
80
80
  export declare function routeMetadataForContent(siteConfig: KernelSiteConfig, page: KernelPage | null): KernelRouteMetadata;
81
+ /**
82
+ * Serializes page robots directives to a `<meta name="robots">` content
83
+ * value, matching what Next.js emits for the same object. Returns '' when
84
+ * no directive is set, so templates can skip the tag.
85
+ */
86
+ export declare function robotsMetaContent(robots: KernelPageRobots | undefined): string;
@@ -53,6 +53,33 @@ export function routeMetadataForContent(siteConfig, page) {
53
53
  title,
54
54
  description,
55
55
  canonical: page?.metadata?.canonical,
56
+ robots: pageRobots(page?.metadata?.robots),
56
57
  siteName: siteConfig.seo?.siteName || siteConfig.title
57
58
  };
58
59
  }
60
+ function pageRobots(value) {
61
+ if (!value || typeof value !== 'object' || Array.isArray(value))
62
+ return undefined;
63
+ const { index, follow } = value;
64
+ const robots = {};
65
+ if (typeof index === 'boolean')
66
+ robots.index = index;
67
+ if (typeof follow === 'boolean')
68
+ robots.follow = follow;
69
+ return robots.index === undefined && robots.follow === undefined ? undefined : robots;
70
+ }
71
+ /**
72
+ * Serializes page robots directives to a `<meta name="robots">` content
73
+ * value, matching what Next.js emits for the same object. Returns '' when
74
+ * no directive is set, so templates can skip the tag.
75
+ */
76
+ export function robotsMetaContent(robots) {
77
+ if (!robots)
78
+ return '';
79
+ const directives = [];
80
+ if (robots.index !== undefined)
81
+ directives.push(robots.index ? 'index' : 'noindex');
82
+ if (robots.follow !== undefined)
83
+ directives.push(robots.follow ? 'follow' : 'nofollow');
84
+ return directives.join(', ');
85
+ }
@@ -1,3 +1,4 @@
1
+ import { isComponentBlock } from './component-block.js';
1
2
  /**
2
3
  * Shared Go/TS limit contract (sdk I-21). These values are pinned against
3
4
  * internal/domain/content/compiler/testdata/limit-conformance.v1.json by
@@ -105,10 +106,7 @@ function* walkValue(value, ancestry, state) {
105
106
  const block = (yield* walkValue(fragmentToBlock(fragment, id), chain, state));
106
107
  return Object.defineProperty(block, fragmentRootScope, { value: true });
107
108
  }
108
- const isBlock = node.$type === 'component'
109
- || (typeof node.component === 'string' && node.component.length > 0
110
- && node.props !== null && typeof node.props === 'object' && !Array.isArray(node.props));
111
- if (isBlock && ++state.blocks > MAX_EXPANDED_BLOCKS) {
109
+ if (isComponentBlock(node) && ++state.blocks > MAX_EXPANDED_BLOCKS) {
112
110
  throw new FragmentResolutionError(`ACI rendered page exceeds ${MAX_EXPANDED_BLOCKS} component blocks`);
113
111
  }
114
112
  const result = {};
@@ -37,10 +37,20 @@ export interface SiteFooter {
37
37
  }
38
38
  export type KernelFooterGroup = FooterGroup;
39
39
  export type KernelFooterColumn = FooterColumn;
40
+ /**
41
+ * Page-level search engine directives. `index: false` renders `noindex` and
42
+ * drops the page from the release sitemap; `follow: false` renders
43
+ * `nofollow`. Absent keys emit nothing (crawler default: index, follow).
44
+ */
45
+ export interface KernelPageRobots {
46
+ index?: boolean;
47
+ follow?: boolean;
48
+ }
40
49
  export interface KernelPageSeo {
41
50
  title?: string;
42
51
  description?: string;
43
52
  canonical?: string;
53
+ robots?: KernelPageRobots;
44
54
  }
45
55
  export interface KernelPageBlock {
46
56
  $type: 'component';
@@ -108,6 +118,7 @@ export interface KernelRouteMetadata {
108
118
  title: string;
109
119
  description: string;
110
120
  canonical?: string;
121
+ robots?: KernelPageRobots;
111
122
  siteName: string;
112
123
  }
113
124
  export interface RenderInput<TPage extends KernelPage = KernelPage, TSiteConfig extends KernelSiteConfig = KernelSiteConfig> {
package/dist/index.d.ts CHANGED
@@ -3,7 +3,8 @@ export { createRegistry, registryLookup } from './registry.js';
3
3
  export type { Registry, RegistryEntry, AnyComponent } from './registry.js';
4
4
  export { blockRef, blockRefArray } from './types/block-ref.js';
5
5
  export type { BlockRef, BlockRefEntry, BlockRefMeta, BlockRefUnion } from './types/block-ref.js';
6
- export type { ComponentContract, ComponentContractID, ComponentContractValidator, ComponentRegistry, ComponentValidationContext, ComponentValidationIssue, InferComponentProps, RootIdentityProps, } from './types/component.js';
6
+ export type { ComponentContract, ComponentContractID, ComponentContractValidator, ComponentRegistry, ComponentValidationContext, ComponentValidationIssue, InferComponentProps, AciRootProps, RootIdentityProps, } from './types/component.js';
7
+ export { aciRootAttrs } from './types/component.js';
7
8
  export { ImageSchema, imageAttrs, primaryImageSource, type Image, type ImageSource, type ImageAttrs, type ImageSlotContract, type SlotOutput, } from './types/image.js';
8
9
  export { LinkSchema, type Link, type LinkTarget, type LinkResolverContext, resolveLink, resolveHref, assertSafeHref, normalizeLinkRel, } from './types/link.js';
9
10
  export { HeadingSchema, InlineContentSchema, RichTextValueSchema, type Heading, type InlineContent, type RichTextValue, } from './types/rich-text.js';
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { defineComponentContract } from './define-component.js';
2
2
  export { createRegistry, registryLookup } from './registry.js';
3
3
  export { blockRef, blockRefArray } from './types/block-ref.js';
4
+ export { aciRootAttrs } from './types/component.js';
4
5
  export { ImageSchema, imageAttrs, primaryImageSource, } from './types/image.js';
5
6
  export { LinkSchema, resolveLink, resolveHref, assertSafeHref, normalizeLinkRel, } from './types/link.js';
6
7
  export { HeadingSchema, InlineContentSchema, RichTextValueSchema, } from './types/rich-text.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ import { isComponentBlock } from '../content/component-block.js';
2
+ /**
3
+ * The one place a renderer seam applies the block root identity (sdk I-31,
4
+ * ADR 0063 §One verifier). The identity props go last, so neither content
5
+ * props nor tenant slotProps can overwrite them.
6
+ *
7
+ * @internal
8
+ */
9
+ export function withAciRootProps(props, block) {
10
+ return {
11
+ ...props,
12
+ 'data-aci-block': block.id,
13
+ 'data-aci-component': block.component,
14
+ };
15
+ }
16
+ /**
17
+ * A copy of a block's content props in which every nested component block
18
+ * (the `rootIdentity` predicate, at any depth: a block slot, or a list inside
19
+ * an item such as `tabs[].blocks`) carries its own identity in its `props`
20
+ * (sdk I-31). A container that renders a child with `{...child.props}` then
21
+ * emits the child's identity; one that calls `renderChildren` re-applies the
22
+ * same values, so nothing doubles. Walk only compiled content, never tenant
23
+ * slotProps: those can hold React elements.
24
+ *
25
+ * @internal
26
+ */
27
+ export function withNestedAciRootProps(props) {
28
+ return stampNested(props);
29
+ }
30
+ function stampNested(value) {
31
+ if (Array.isArray(value))
32
+ return value.map(stampNested);
33
+ if (!isPlainObject(value))
34
+ return value;
35
+ const out = {};
36
+ for (const [key, child] of Object.entries(value))
37
+ out[key] = stampNested(child);
38
+ if (isComponentBlock(out) && typeof out.id === 'string' && out.id !== ''
39
+ && typeof out.component === 'string' && isPlainObject(out.props)) {
40
+ out.props = withAciRootProps(out.props, { id: out.id, component: out.component });
41
+ }
42
+ return out;
43
+ }
44
+ function isPlainObject(value) {
45
+ if (typeof value !== 'object' || value === null)
46
+ return false;
47
+ const proto = Object.getPrototypeOf(value);
48
+ return proto === Object.prototype || proto === null;
49
+ }
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Component, Fragment } from 'react';
4
+ import { withAciRootProps, withNestedAciRootProps } from '../internal/aci-root.js';
4
5
  import { ErrorPageInstrumentation } from './error-page-instrumentation.js';
5
6
  import { ErrorPageMarker } from './error-page-marker.js';
6
7
  /** The build-time serialized payload, or null when the define is absent. */
@@ -74,13 +75,11 @@ function renderClientBlocks(blocks, registry) {
74
75
  if (!entry)
75
76
  return null;
76
77
  const Component = entry.component;
77
- const componentProps = { ...block.props };
78
+ // sdk I-31 (ADR 0051): the error seat is an injection seam too.
79
+ const componentProps = withAciRootProps(withNestedAciRootProps(block.props), block);
78
80
  if (entry.contract.blockSlots) {
79
81
  componentProps.renderChildren = (children) => renderClientBlocks(children, registry);
80
82
  }
81
- // sdk I-19 (ADR 0051): the error seat is an injection seam too.
82
- componentProps['data-aci-block'] = block.id;
83
- componentProps['data-aci-component'] = block.component;
84
83
  // Index+id composite: a repeated fragment placement repeats its authored
85
84
  // id, and sibling keys must stay unique for React reconciliation.
86
85
  return _jsx(Component, { ...componentProps }, `${index}:${block.id}`);
package/dist/next/page.js CHANGED
@@ -57,6 +57,7 @@ export function createPage(registry, options) {
57
57
  title: meta.title,
58
58
  description: meta.description,
59
59
  alternates: meta.canonical ? { canonical: meta.canonical } : undefined,
60
+ robots: meta.robots,
60
61
  openGraph: {
61
62
  title: meta.title,
62
63
  description: meta.description,
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { withAciRootProps, withNestedAciRootProps } from '../internal/aci-root.js';
2
3
  export async function renderBlock(block, registry, key, extraProps) {
3
4
  const entry = registry.find((r) => r.id === block.component);
4
5
  if (!entry) {
@@ -6,14 +7,7 @@ export async function renderBlock(block, registry, key, extraProps) {
6
7
  }
7
8
  const Component = entry.component;
8
9
  const hasBlockSlots = !!entry.contract.blockSlots;
9
- // sdk I-19 (ADR 0051): root identity props are applied last so neither
10
- // content props nor tenant slotProps can overwrite them.
11
- const componentProps = {
12
- ...block.props,
13
- ...extraProps,
14
- 'data-aci-block': block.id,
15
- 'data-aci-component': block.component,
16
- };
10
+ const componentProps = withAciRootProps({ ...withNestedAciRootProps(block.props), ...extraProps }, block);
17
11
  if (hasBlockSlots) {
18
12
  componentProps.renderChildren = (blocks) => renderBlocks(blocks, registry, extraProps);
19
13
  }
@@ -0,0 +1,2 @@
1
+ import type { LoadedSite } from './types.js';
2
+ export declare function openAstroSite(siteDir: string, explicit?: readonly string[]): Promise<LoadedSite>;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Astro adapter: renders one registered component with the Astro Container
3
+ * API over a Vite server that Astro configures for the site
4
+ * (`getViteConfig`), the way Astro documents testing components. No site
5
+ * build and no emitted HTML, so static and server output are covered alike.
6
+ * astro, its container, and vite resolve from the site, never from the SDK.
7
+ */
8
+ import { createRequire } from 'node:module';
9
+ import { join, relative, sep } from 'node:path';
10
+ import { pathToFileURL } from 'node:url';
11
+ import { createRenderChildren } from '../../astro/index.js';
12
+ import { classifyLoadError, findRegistryFiles, mergeRegistries, registryExport } from './site.js';
13
+ export async function openAstroSite(siteDir, explicit = []) {
14
+ // Every file that calls createRegistry(, plus any registry the site names explicitly.
15
+ const registryFiles = [...new Set([...findRegistryFiles(siteDir), ...explicit])].sort();
16
+ if (registryFiles.length === 0)
17
+ throw new Error('no registry file imports createRegistry from @gradial/aci');
18
+ const fromSite = createRequire(join(siteDir, 'package.json'));
19
+ const importFrom = async (resolve, specifier) => (await import(pathToFileURL(resolve(specifier)).href));
20
+ const { getViteConfig } = await importFrom(fromSite.resolve, 'astro/config');
21
+ const { experimental_AstroContainer } = await importFrom(fromSite.resolve, 'astro/container');
22
+ const fromAstro = createRequire(fromSite.resolve('astro/package.json'));
23
+ const { createServer } = await importFrom(fromAstro.resolve, 'vite');
24
+ const vite = await withoutContentWatch(async () => {
25
+ // An isolated SSR render needs no client dependency optimizer and no dev
26
+ // toolbar; both would otherwise run in the background past close().
27
+ const config = await getViteConfig({
28
+ logLevel: 'silent',
29
+ optimizeDeps: { noDiscovery: true, include: [] },
30
+ }, { root: siteDir, logLevel: 'silent', devToolbar: { enabled: false } })({ mode: 'development', command: 'serve' });
31
+ const server = (config.server ?? {});
32
+ return createServer({
33
+ ...config,
34
+ server: { ...server, middlewareMode: true, hmr: false, ws: false, watch: null },
35
+ appType: 'custom',
36
+ });
37
+ });
38
+ try {
39
+ // Every registry file is loaded through Astro's own Vite pipeline; one
40
+ // that cannot load is reported and does not hide the others.
41
+ const registries = [];
42
+ const unloadable = [];
43
+ for (const registryFile of registryFiles) {
44
+ const rel = relative(siteDir, registryFile);
45
+ try {
46
+ const registry = registryExport(await vite.ssrLoadModule('/' + rel.split(sep).join('/')));
47
+ if (registry)
48
+ registries.push(registry);
49
+ else
50
+ unloadable.push({ file: rel, reason: 'does not export the createRegistry result' });
51
+ }
52
+ catch (err) {
53
+ unloadable.push({ file: rel, reason: classifyLoadError(err).reason });
54
+ }
55
+ }
56
+ const registry = mergeRegistries(registries);
57
+ const container = await experimental_AstroContainer.create();
58
+ return {
59
+ registry,
60
+ adapter: createAstroAdapter(registry, container, siteDir),
61
+ unloadable,
62
+ close: () => vite.close(),
63
+ };
64
+ }
65
+ catch (err) {
66
+ await vite.close();
67
+ throw err;
68
+ }
69
+ }
70
+ function createAstroAdapter(registry, container, siteDir) {
71
+ const renderChildren = createRenderChildren(registry);
72
+ return {
73
+ id: 'astro',
74
+ async renderBlock(block) {
75
+ const [{ Component, props }] = renderChildren([block]);
76
+ if (!Component)
77
+ throw new Error(`no registered component "${block.component}"`);
78
+ return container.renderToString(Component, { props });
79
+ },
80
+ sourceFileFor(componentId) {
81
+ // Astro's compiler stamps every component factory with its source path.
82
+ const component = registry.find((entry) => entry.id === componentId)?.component;
83
+ return typeof component?.moduleId === 'string' ? relative(siteDir, component.moduleId) : undefined;
84
+ },
85
+ };
86
+ }
87
+ // withAci() recompiles content when a dev server starts; an isolated render
88
+ // needs neither the recompile nor the watcher.
89
+ async function withoutContentWatch(fn) {
90
+ const previous = process.env.ACI_DISABLE_CONTENT_WATCH;
91
+ process.env.ACI_DISABLE_CONTENT_WATCH = '1';
92
+ try {
93
+ return await fn();
94
+ }
95
+ finally {
96
+ if (previous === undefined)
97
+ delete process.env.ACI_DISABLE_CONTENT_WATCH;
98
+ else
99
+ process.env.ACI_DISABLE_CONTENT_WATCH = previous;
100
+ }
101
+ }
@@ -0,0 +1,9 @@
1
+ import type { Registry } from '../../registry.js';
2
+ import type { FrameworkAdapter, LoadedSite } from './types.js';
3
+ /** The react-dom/server surface the adapter needs. */
4
+ interface ReactRuntime {
5
+ renderToStaticMarkup(element: unknown): string;
6
+ }
7
+ export declare function openNextSite(siteDir: string, explicit?: readonly string[]): Promise<LoadedSite>;
8
+ export declare function createNextAdapter(registry: Registry, react: ReactRuntime, sourceFileFor: (componentId: string) => string | undefined): FrameworkAdapter;
9
+ export {};