@elytracms/next 0.0.87 → 0.0.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -6,15 +6,16 @@ export { ComponentImplementations, RenderAsset } from '@elytracms/core/runtime-r
6
6
  import { HostComponentEntry } from '@elytracms/core/studio-core';
7
7
  import { ResolvePageResult, ResolvedAsset, Perspective, ContentClient, CacheTags, ContentDocumentSource, ResolvedDocument } from '@elytracms/core/content';
8
8
  export { Perspective } from '@elytracms/core/content';
9
- import { LocaleConfig, Locale, RouteRecord, CollectionDef, RedirectRecord } from '@elytracms/core/cms-core';
10
- export { CmsDocument, CollectionDef, FieldDef, Locale, LocaleConfig, LocaleRequest, LocaleStrategy, LocaleUrlPath, RedirectRecord, RouteRecord, documentSchema, fallbackHomeRoutes, localeConfigSchema, localeUrlPath, mergeRouteRecords, redirectRecordSchema, resolveLocale, routeRecordSchema, splitLocalePath, textDirection } from '@elytracms/core/cms-core';
9
+ import { LocaleConfig, Locale, RouteRecord, CollectionDef, RedirectRecord, LocaleConfigInput } from '@elytracms/core/cms-core';
10
+ export { CmsDocument, CollectionDef, FieldDef, Locale, LocaleConfig, LocaleConfigInput, LocaleRequest, LocaleStrategy, LocaleUrlPath, RedirectRecord, RouteRecord, documentSchema, fallbackHomeRoutes, localeConfigSchema, localeUrlPath, mergeRouteRecords, parseLocaleConfig, redirectRecordSchema, resolveLocale, routeRecordSchema, splitLocalePath, textDirection } from '@elytracms/core/cms-core';
11
11
  import { AssetRecord, PersistenceAdapter } from '@elytracms/core/persistence';
12
12
  export { AssetRecord } from '@elytracms/core/persistence';
13
- import { Metadata, MetadataRoute } from 'next';
14
- import { z } from 'zod';
15
- export { z } from 'zod';
13
+ import { ResolvingMetadata, Metadata, ResolvedMetadata, MetadataRoute } from 'next';
14
+ import { RevalidationEvent } from '@elytracms/core/revalidation';
15
+ export { REVALIDATE_SIGNATURE_HEADER, RevalidationEvent, RevalidationEventKind, revalidationEventSchema, signRevalidationBody, signedRevalidationRequest } from '@elytracms/core/revalidation';
16
16
  import { ImageLoader } from 'next/image';
17
17
  import { HostImageRenderer } from './host-image.js';
18
+ export { z } from 'zod';
18
19
  export { ComponentNode } from '@elytracms/core/project-graph';
19
20
 
20
21
  /**
@@ -259,9 +260,9 @@ interface ContentSnapshotOptions {
259
260
  * and EC-519 collapsed the two declarations into one; this was the last place
260
261
  * in the chain still inventing the value, and it sits in the package that
261
262
  * resolves the URLs. Hand it the project's `LocaleConfig` — the one the
262
- * config declares.
263
+ * config declares. Parsed here (EC-580): a `default` outside `locales` throws.
263
264
  */
264
- locales: LocaleConfig;
265
+ locales: LocaleConfigInput;
265
266
  }
266
267
  interface ContentSnapshot extends ContentSource {
267
268
  readonly projectId: string;
@@ -275,8 +276,8 @@ interface ContentSnapshot extends ContentSource {
275
276
  */
276
277
  interface StaticContentData {
277
278
  projectId: string;
278
- /** Locale configuration — **required**, never defaulted (see EC-530 above). */
279
- locales: LocaleConfig;
279
+ /** Locale configuration — **required**, never defaulted (see EC-530 above); parsed (EC-580). */
280
+ locales: LocaleConfigInput;
280
281
  /** Route records served by `resolvePage` (EC-019). */
281
282
  routes?: readonly RouteRecord[];
282
283
  /** Redirect records served by `resolvePage` (EC-019/020). */
@@ -443,9 +444,9 @@ declare function resolveCanvasPage(source: ContentSource, url: string, perspecti
443
444
 
444
445
  interface CanvasCacheOptions {
445
446
  /**
446
- * Project scope prefix for every tag (`p:<scope>:…`) — must be the project
447
- * id the studio publishes under, so the webhook dispatcher's tags match.
448
- * Omit only when the emitter also emits unscoped tags (see `cache.ts`).
447
+ * Project scope prefix for every tag (`p:<scope>:…`) — must be the SAME scope
448
+ * the revalidate receiver derives its tags with (`createRevalidateRoute`'s
449
+ * `scope`; the delivery kit hands its one `contentScope` to both, EC-579).
449
450
  */
450
451
  scope?: string;
451
452
  /**
@@ -538,13 +539,59 @@ interface CanvasRouteProps {
538
539
  interface CanvasRoute {
539
540
  /** The page component: `export default route.Page`. */
540
541
  Page: (props: CanvasRouteProps) => Promise<ReactNode>;
541
- /** Next metadata hook: `export const generateMetadata = route.generateMetadata`. */
542
- generateMetadata: (props: CanvasRouteProps) => Promise<Metadata>;
542
+ /**
543
+ * Next metadata hook: `export const generateMetadata = route.generateMetadata`.
544
+ * Next passes the parent segment's resolving metadata as the second argument;
545
+ * the route merges the page's tags over it (EC-610). A caller that invokes this
546
+ * by hand (the scaffold's `app/page.tsx`) forwards its own `parent`.
547
+ *
548
+ * `parent` is REQUIRED, not optional (EC-614): Next's webpack build validates a
549
+ * page's `generateMetadata` with `SecondArg<…> extends ResolvingMetadata`, and an
550
+ * optional parameter reads as `ResolvingMetadata | undefined`, which fails that
551
+ * constraint — a consumer re-exporting this hook could not `next build --webpack`
552
+ * on 0.0.88 (the Turbopack validator is lenient, which is how the gate missed it).
553
+ */
554
+ generateMetadata: (props: CanvasRouteProps, parent: ResolvingMetadata) => Promise<Metadata>;
543
555
  }
544
556
  /** Create the `{ Page, generateMetadata }` pair for `app/[[...slug]]/page.tsx`. */
545
557
  declare function createCanvasRoute(options: CanvasRouteOptions): CanvasRoute;
546
558
 
559
+ /**
560
+ * EC-531, narrowed by EC-610 (owner ruling 3, 2026-10-23): `og:locale` ONLY in the
561
+ * shape Open Graph specifies — `language_TERRITORY` with an underscore (`de_DE`),
562
+ * where BCP 47 writes `de-DE`. The value is the page's RESOLVED locale
563
+ * (`result.tags.locale`, the same fact the router used to pick the content). A
564
+ * bare language code (`de`) is outside that format, so the kit emits nothing for
565
+ * it and the layout's own value stands through the merge: inventing a territory
566
+ * (`de` → `de_DE`) would be wrong exactly where it matters (Austria, the UK,
567
+ * Brazil, Latin America), and a wrong valid value looks deliberate.
568
+ */
569
+ declare function openGraphLocale(locale: string): string | null;
547
570
  declare function canvasPageMetadata(result: ResolvePageResult, assets?: readonly ResolvedAsset[]): Metadata;
571
+ /**
572
+ * EC-610 (owner ruling 1, 2026-10-13): **the site provides the default tags, and
573
+ * the page's tags are merged on top** — a value the page states overrides the
574
+ * site's, a value the page leaves empty falls back to the site's.
575
+ *
576
+ * Next merges metadata SHALLOWLY (read: `generate-metadata.md`, "Merging"): a
577
+ * page segment's `openGraph` object REPLACES the layout's whole `openGraph`, so a
578
+ * site-wide `type`, `siteName`, site image or `locale` declared once in the root
579
+ * layout never reached a canvas page, and both real sites hand-rolled around it
580
+ * differently (NK-039's page hook, Konditorei's post bypass). This is the one
581
+ * merge, applied by the canvas route to whatever builds the page's metadata (the
582
+ * kit's default or the host's `metadata` option): every object-valued key the
583
+ * page sets (`openGraph`, `twitter`, `robots`, `alternates`, …) is merged one
584
+ * level deep over the parent's RESOLVED value; a key the page does not set is left
585
+ * to Next, which inherits it from the parent as before; a scalar or array the
586
+ * page sets wins outright.
587
+ *
588
+ * The parent's resolved shapes spread back cleanly: Next hands `generateMetadata`
589
+ * a `ResolvedMetadata` whose URLs are already strings, whose `images` are
590
+ * descriptor objects and whose titles are `{ absolute, template }` — all of which
591
+ * `Metadata` accepts (Next's own "extend rather than replace" example spreads
592
+ * `(await parent).openGraph?.images`).
593
+ */
594
+ declare function mergeMetadataOverParent(parent: ResolvedMetadata | null | undefined, page: Metadata): Metadata;
548
595
 
549
596
  /**
550
597
  * `sitemap.xml` from published routes (EC-171): walk the same route records
@@ -648,10 +695,17 @@ declare function createCanvasSitemap(options: CreateCanvasSitemapOptions): () =>
648
695
  * Cache-tag pipeline (EC-146, vision AD-5) — the pure half.
649
696
  *
650
697
  * This module defines (a) the **canonical Next.js tag string format** mapping
651
- * the accessor-level `CacheTags` (EC-143) onto `revalidateTag` keys, and (b)
652
- * the **tag-discovery caching logic** the catch-all route helper runs inside
653
- * `unstable_cache`. No Next.js imports — everything here is unit-testable;
654
- * `route.tsx` injects the real `unstable_cache` via {@link CacheWrapper}.
698
+ * the accessor-level `CacheTags` (EC-143) onto `revalidateTag` keys, (b) the
699
+ * **tag derivation for a revalidation event** ({@link revalidationTags}, EC-579 —
700
+ * the receiver's half of the one emitter), and (c) the **tag-discovery caching
701
+ * logic** the catch-all route helper runs inside `unstable_cache`. No Next.js
702
+ * imports — everything here is unit-testable; `route.tsx` injects the real
703
+ * `unstable_cache` via {@link CacheWrapper}.
704
+ *
705
+ * This is the ONE tag module: the entries are tagged here and the invalidations
706
+ * are derived here, from the same formatters and the same scope. Before EC-579 the
707
+ * format was mirrored twice more (the studio's browser dispatcher and an inline
708
+ * literal in the Convex emitter), and each emitter brought its own scope.
655
709
  *
656
710
  * ## Tag format
657
711
  *
@@ -669,8 +723,8 @@ declare function createCanvasSitemap(options: CreateCanvasSitemapOptions): () =>
669
723
  * (alt/dimensions/re-upload) drops exactly the pages that baked it. Not
670
724
  * locale-scoped — an asset record is locale-agnostic.
671
725
  * - `project` — the whole-project sweep tag attached to **every** cached
672
- * entry; graph publish/unpublish emits it (a layout or route change can
673
- * affect any page, so the honest v1 fallback is a full project sweep).
726
+ * entry; every revalidation event that changes live output invalidates it
727
+ * (see {@link revalidationTags} for why, until EC-515).
674
728
  *
675
729
  * ## Project scope
676
730
  *
@@ -678,16 +732,11 @@ declare function createCanvasSitemap(options: CreateCanvasSitemapOptions): () =>
678
732
  * `p:prj_aurora:doc:post:post-1`). The cost is a few bytes per tag and it
679
733
  * keeps multi-project hosts (one Next app serving several builder projects)
680
734
  * from cross-invalidating, so the prefix is **on whenever the host knows its
681
- * project id** — decision: include it. The emitter (the studio's webhook
682
- * dispatcher, `packages/studio/src/lib/webhooks`) always scopes with the
683
- * operation's `projectId`; a host that omits `cache.scope` therefore will not
684
- * match studio-emitted tags. Configure `cache.scope` with the same project id
685
- * the studio publishes under.
686
- *
687
- * The studio-side emitter cannot depend on this package (it would drag the
688
- * `next` peer dependency into the studio bundle), so
689
- * `packages/studio/src/lib/webhooks/tags.ts` mirrors these formatters; tests on
690
- * both sides pin the exact literal strings to keep them in lock-step.
735
+ * project id** — decision: include it. Since EC-579 the scope has ONE owner: the
736
+ * host. The webhook carries an event, not tags, and the receiver derives the
737
+ * tags with the same scope its cache entries are written under (the delivery
738
+ * kit hands both the one `contentScope`), so emitter and host can no longer
739
+ * disagree about it.
691
740
  */
692
741
  /** Apply the optional multi-project scope prefix to one tag. */
693
742
  declare function scopeCacheTag(tag: string, scope?: string): string;
@@ -701,9 +750,8 @@ declare function collectionCacheTag(collection: string, locale: string, scope?:
701
750
  /** Tag for one resolved route path (or redirect-chain hop) in one locale. */
702
751
  declare function routeCacheTag(pattern: string, locale: string, scope?: string): string;
703
752
  /**
704
- * The whole-project sweep tag. Attached to every cached page entry; emitted
705
- * on graph publish/unpublish (and as the emitter's honest fallback when a
706
- * change's precise tag set is unknowable).
753
+ * The whole-project sweep tag. Attached to every cached page entry; derived for
754
+ * every revalidation event that changes live output ({@link revalidationTags}).
707
755
  */
708
756
  declare function projectSweepTag(scope?: string): string;
709
757
  /**
@@ -712,6 +760,38 @@ declare function projectSweepTag(scope?: string): string;
712
760
  * cache entry — including the project sweep tag.
713
761
  */
714
762
  declare function nextCacheTags(tags: CacheTags, scope?: string): string[];
763
+ /** The receiver's own project — what its cached entries are tagged with. */
764
+ interface RevalidationProject {
765
+ /** The tag scope of this host's cache entries (`p:<scope>:…`), if any. */
766
+ scope?: string | undefined;
767
+ /** Every locale this host serves — one `collection:` tag per locale. */
768
+ locales: readonly string[];
769
+ }
770
+ /**
771
+ * The tags one revalidation event invalidates on this host — sorted, deduplicated,
772
+ * scoped with the receiver's OWN scope, so the emitter never needs to know it.
773
+ *
774
+ * - `publish` / `unpublish` / `delete` → `doc:<collection>:<id>` (its detail page and
775
+ * every cached list that contained it) + `collection:<collection>:<locale>` for
776
+ * EVERY locale this host serves (list membership may have changed) + the sweep;
777
+ * - `redirects` → the sweep (routing can change any URL; see the `redirects` trigger
778
+ * in the studio's `convex/triggers.ts` for why a targeted `route:` tag is wrong);
779
+ * - `asset` → `asset:<id>` + the sweep (a page reading the asset through a document
780
+ * asset FIELD carries no `asset:` tag — EC-566 D16, EC-589);
781
+ * - `probe` → nothing (the doctor's no-op ping).
782
+ *
783
+ * **Why every live-output event also carries the project sweep — a deliberate,
784
+ * measured deviation from the EC-579 ruling's text, which lists the sweep only for
785
+ * redirects and asset removes.** A self-hosted Next host honours ONLY the baseline
786
+ * tags of a cache read (measured in EC-515 on Next 16.2.10 — see
787
+ * {@link loadCachedEntry}): a discovered `doc:` / `collection:` / `asset:` tag
788
+ * invalidates nothing there, silently, and Vercel is unmeasured. Until EC-515's
789
+ * option D makes the precise tags honoured, the precise tags alone would turn
790
+ * "revalidates coarsely" into "never revalidates" on every self-hosted host — so the
791
+ * sweep stays on every event, exactly as the Convex path sent it before. Dropping it
792
+ * from a publish is EC-515's job, together with the read that makes it safe.
793
+ */
794
+ declare function revalidationTags(event: RevalidationEvent, project: RevalidationProject): string[];
715
795
  /**
716
796
  * The slice of `unstable_cache` the discovery logic needs, injectable so the
717
797
  * logic is testable against a faithful in-memory model (and so the test model
@@ -809,11 +889,11 @@ interface CachedEntry<T> {
809
889
  * webhook are all correct — the read simply never consults them.
810
890
  *
811
891
  * Consequence for a self-hosted deployment: invalidation granularity collapses
812
- * to whatever is in `baselineTags`. The studio's Convex write chokepoint emits
813
- * the project sweep on every publish, so instant publish still *works* there,
814
- * coarsely; the browser dispatcher's targeted `doc:`/`collection:` tag set
815
- * (`packages/studio/src/lib/webhooks/tags.ts`) does not reach a self-hosted host
816
- * at all. Vercel-hosted deployments are unaffected in practice — both consumer
892
+ * to whatever is in `baselineTags`. {@link revalidationTags} therefore adds the
893
+ * project sweep to every event that changes live output (EC-579), so instant
894
+ * publish still *works* there, coarsely; its targeted `doc:`/`collection:`/`asset:`
895
+ * tags do not reach a self-hosted host's entries at all. Vercel-hosted deployments
896
+ * are unaffected in practice — both consumer
817
897
  * sites were measured healthy on 2026-08-01 — which is consistent with Vercel's
818
898
  * cache handler purging by the entry's stored tags, though that has **not** been
819
899
  * measured here.
@@ -826,90 +906,80 @@ interface CachedEntry<T> {
826
906
  declare function loadCachedEntry<T>(cache: CacheWrapper, keyParts: string[], baselineTags: readonly string[], load: () => Promise<CachedEntry<T>>): Promise<CachedEntry<T>>;
827
907
 
828
908
  /**
829
- * Pure signature/payload logic of the revalidation webhook receiver (EC-146),
830
- * kept free of Next.js imports so it is unit-testable. `revalidate.ts` wires
831
- * it into a route handler with `revalidateTag`.
832
- *
833
- * Scheme: HMAC-SHA256 over the **raw request body** with a shared secret,
834
- * carried as `x-elytra-signature: sha256=<hex>`. The emitter (the studio's
835
- * webhook dispatcher) signs the exact JSON string it sends; the receiver
836
- * verifies over the raw text *before* parsing, with a timing-safe compare.
909
+ * Pure request logic of the revalidation webhook receiver (EC-146), kept free of
910
+ * Next.js imports so it is unit-testable. `revalidate.ts` wires it into a route
911
+ * handler with `revalidateTag`.
912
+ *
913
+ * The wire — event schema, signer, header — is NOT declared here: it is the one
914
+ * module `@elytracms/core/revalidation`, shared with the emitter (the studio's
915
+ * Convex write chokepoint) and the doctor probe (EC-579). This file only decides
916
+ * the HTTP answer: auth before parsing, then the event.
837
917
  */
838
- /** Header carrying the HMAC signature. */
839
- declare const REVALIDATE_SIGNATURE_HEADER = "x-elytra-signature";
840
- /** The webhook body: which tags to revalidate, and why. */
841
- declare const revalidatePayloadSchema: z.ZodObject<{
842
- projectId: z.ZodString;
843
- event: z.ZodString;
844
- tags: z.ZodArray<z.ZodString>;
845
- timestamp: z.ZodString;
846
- }, z.core.$strip>;
847
- type RevalidatePayload = z.infer<typeof revalidatePayloadSchema>;
848
- /** Compute the signature header value for a raw body (emitter/test side). */
849
- declare function signRevalidateBody(rawBody: string, secret: string): string;
850
918
  type RevalidateEvaluation = {
851
919
  ok: true;
852
- payload: RevalidatePayload;
920
+ event: RevalidationEvent;
853
921
  } | {
854
922
  ok: false;
855
923
  status: 400 | 401;
856
924
  message: string;
857
925
  };
858
926
  /**
859
- * Evaluate one webhook request: verify the HMAC first (auth before parsing),
860
- * then validate the payload shape.
861
- *
862
- * - unconfigured (empty) secret → 401 for every request: the endpoint stays
863
- * closed until a secret is deliberately configured (mirrors EC-144 preview);
864
- * - missing/malformed/mismatched signature → 401 (timing-safe compare);
865
- * - unparseable or schema-invalid body → 400 with no tag revalidated.
927
+ * Evaluate one webhook request: verify the HMAC first (auth before parsing), then
928
+ * read the event.
929
+ *
930
+ * - unconfigured (empty) secret → 401 for every request: the endpoint stays closed
931
+ * until a secret is deliberately configured (mirrors EC-144 preview);
932
+ * - missing/malformed/mismatched signature → 401 (constant-time verify);
933
+ * - unparseable or schema-invalid body → 400, nothing revalidated; a body in the
934
+ * pre-EC-579 tag-list shape gets its own named 400 (an older Elytra emitter —
935
+ * deploy the matching studio functions), because a version skew between the
936
+ * Convex deployment and this host is the one way a correctly signed body can
937
+ * arrive in the wrong shape.
866
938
  */
867
- declare function evaluateRevalidateRequest(rawBody: string, signature: string | null | undefined, secret: string | undefined): RevalidateEvaluation;
939
+ declare function evaluateRevalidateRequest(rawBody: string, signature: string | null | undefined, secret: string | undefined): Promise<RevalidateEvaluation>;
868
940
 
869
941
  /**
870
942
  * Revalidation webhook route handler (EC-146): the receiving end of the
871
- * publish → live-in-seconds pipeline. The studio's dispatcher POSTs a signed
872
- * `{projectId, event, tags, timestamp}` payload; this handler verifies the
873
- * HMAC over the raw body and calls `revalidateTag` for each tag, dropping
874
- * exactly the cached entries whose fetch-time tag sets contained them.
875
- *
876
- * Server-only by construction — consumed from an App Router route handler
877
- * file, never from client bundles.
943
+ * publish → live-in-seconds pipeline. The studio's Convex write chokepoint — the ONE
944
+ * emitter (EC-579) — POSTs a signed EVENT (`{ kind, collection?, id? }`, the wire in
945
+ * `@elytracms/core/revalidation`); this handler verifies the HMAC over the raw body,
946
+ * derives the tags from the event with this host's OWN scope and locales
947
+ * (`revalidationTags`, `cache.ts`) and calls `revalidateTag` for each, dropping the
948
+ * cached entries whose fetch-time tag sets contained them.
949
+ *
950
+ * Server-to-server only: the emitter runs in Convex, so there is no CORS and no
951
+ * `OPTIONS` preflight (the studio's browser dispatcher that needed them is deleted).
952
+ * Server-only by construction — consumed from an App Router route handler file,
953
+ * never from client bundles.
878
954
  */
879
- interface RevalidateRouteOptions {
955
+ interface RevalidateRouteOptions extends RevalidationProject {
880
956
  /**
881
957
  * Shared HMAC secret. An empty/undefined secret rejects every request with
882
958
  * 401 — the endpoint stays closed until deliberately configured.
883
959
  */
884
960
  secret: string | undefined;
885
- /**
886
- * CORS origin allowed to call this endpoint (e.g. the studio's origin).
887
- * The v1 dispatcher is a browser `fetch` from the studio, and the custom
888
- * signature header always triggers a CORS preflight — so cross-origin
889
- * studio → host dispatch only works when this is set. Server-side emitters
890
- * (the Convex action variant, CLIs, CI) need no CORS and may leave it
891
- * unset. Never use `*` with a real secret-bearing deployment unless you
892
- * accept that any origin may *attempt* deliveries (they still need the
893
- * secret to have any effect).
894
- */
895
- allowOrigin?: string;
896
961
  }
897
962
  interface RevalidateRouteHandlers {
898
963
  POST(request: Request): Promise<Response>;
899
- OPTIONS(): Promise<Response>;
900
964
  }
901
965
  /**
902
- * Create the webhook route handlers for `app/api/revalidate/route.ts`:
966
+ * Create the webhook route handler for `app/api/revalidate/route.ts`. Through the
967
+ * delivery kit it is `export const { POST } = elytra.revalidateRoute()`, which hands
968
+ * it the kit's own scope and locales — the ones its cached entries carry. Wiring it
969
+ * by hand, pass the SAME scope your canvas route caches under:
903
970
  *
904
971
  * ```ts
905
- * export const { POST, OPTIONS } = createRevalidateRoute({
972
+ * export const { POST } = createRevalidateRoute({
906
973
  * secret: process.env.REVALIDATE_SECRET,
974
+ * scope: process.env.ELYTRA_PROJECT_ID,
975
+ * locales: ['en', 'de'],
907
976
  * })
908
977
  * ```
909
978
  *
910
- * Unsigned/invalid requests are rejected with 401 (timing-safe HMAC check,
911
- * see `evaluateRevalidateRequest`); valid requests revalidate every carried
912
- * tag and answer `{ revalidated: tags }`.
979
+ * Unsigned/invalid requests are rejected with 401 (constant-time HMAC verify, see
980
+ * `evaluateRevalidateRequest`), signed-but-unreadable ones with 400; a valid event
981
+ * revalidates its derived tags and answers `{ revalidated: tags }` (`[]` for the
982
+ * doctor's `probe`).
913
983
  */
914
984
  declare function createRevalidateRoute(options: RevalidateRouteOptions): RevalidateRouteHandlers;
915
985
 
@@ -1112,7 +1182,9 @@ declare function elytraHostImage(options?: ElytraHostImageOptions): HostImageRen
1112
1182
  * | | equal the Convex deployment's own env var) |
1113
1183
  * | `REVALIDATE_SECRET` | Instant-publish webhook HMAC secret (must equal the |
1114
1184
  * | | deployment's `CANVAS_REVALIDATE_SECRET`) |
1115
- * | `STUDIO_ORIGIN` | CORS origin for the studio's browser-dispatch path |
1185
+ *
1186
+ * EC-579 retired `STUDIO_ORIGIN` (the CORS origin of the studio's deleted browser
1187
+ * dispatcher): the one emitter is the Convex deployment, server to server.
1116
1188
  */
1117
1189
  /** The kit's documented env names (single source of truth, also used in docs/tests). */
1118
1190
  declare const ELYTRA_HOST_ENV_KEYS: {
@@ -1120,14 +1192,12 @@ declare const ELYTRA_HOST_ENV_KEYS: {
1120
1192
  readonly projectId: "ELYTRA_PROJECT_ID";
1121
1193
  readonly draftToken: "DRAFT_PREVIEW_TOKEN";
1122
1194
  readonly revalidateSecret: "REVALIDATE_SECRET";
1123
- readonly studioOrigin: "STUDIO_ORIGIN";
1124
1195
  };
1125
1196
  interface ElytraHostEnv {
1126
1197
  convexUrl?: string;
1127
1198
  projectId?: string;
1128
1199
  draftToken?: string;
1129
1200
  revalidateSecret?: string;
1130
- studioOrigin?: string;
1131
1201
  }
1132
1202
  /**
1133
1203
  * Resolve the kit's env bag from a raw env record (`process.env` by default at
@@ -1298,7 +1368,7 @@ declare function createPreviewDisableRoute(): PreviewRouteHandlers;
1298
1368
  * export const generateMetadata = route.generateMetadata
1299
1369
  *
1300
1370
  * // app/api/revalidate/route.ts
1301
- * export const { POST, OPTIONS } = elytra.revalidateRoute()
1371
+ * export const { POST } = elytra.revalidateRoute()
1302
1372
  *
1303
1373
  * // app/api/draft/route.ts — GET /api/draft?token=…&redirect=/path
1304
1374
  * export const { GET } = elytra.previewRoute()
@@ -1324,7 +1394,7 @@ interface ElytraHostOptions {
1324
1394
  * Locale config — config-owned (AD-11), never read from the snapshot.
1325
1395
  *
1326
1396
  * **Import it, do not restate it** (EC-519). This is the *same*
1327
- * {@link LocaleConfig} `elytra.config.ts` takes, so a project declares its
1397
+ * {@link LocaleConfigInput} `elytra.config.ts` takes, so a project declares its
1328
1398
  * locales once and hands that one binding to both mounts. It stays a host
1329
1399
  * option because the engine cannot know where a repo keeps the value — but it
1330
1400
  * is no longer an option you have to *retype*: since EC-532 the delivery
@@ -1336,8 +1406,14 @@ interface ElytraHostOptions {
1336
1406
  * fact `locales` + `defaultLocale`, and in our own demo the two drifted — the
1337
1407
  * host said `['en']`, the config said `['en','de']`, and every `/de` URL 404'd
1338
1408
  * silently for weeks (EC-512).
1409
+ *
1410
+ * The DECLARED shape (EC-580): the kit parses it into the engine's `LocaleConfig`
1411
+ * where every content source is assembled (`createStaticContentSource` — the
1412
+ * empty source is built at boot), so a `default` outside `locales` stops the host
1413
+ * with a located message instead of serving — the same rule the studio's
1414
+ * workspace gate applies to the same value.
1339
1415
  */
1340
- locales: LocaleConfig;
1416
+ locales: LocaleConfigInput;
1341
1417
  /** CMS collection schema — config-owned (AD-11). */
1342
1418
  collections: readonly CollectionDef[];
1343
1419
  /** Routes as code (AD-3/AD-11): records, or the `(collections) => records` derive. */
@@ -1384,7 +1460,7 @@ interface ElytraHostOptions {
1384
1460
  /**
1385
1461
  * Env bag; defaults to `process.env`. Documented names (see `host-env.ts`):
1386
1462
  * `ELYTRA_CONVEX_URL`, `ELYTRA_PROJECT_ID`, `DRAFT_PREVIEW_TOKEN`,
1387
- * `REVALIDATE_SECRET`, `STUDIO_ORIGIN`.
1463
+ * `REVALIDATE_SECRET`.
1388
1464
  */
1389
1465
  env?: Record<string, string | undefined>;
1390
1466
  /**
@@ -1415,13 +1491,17 @@ interface HostCanvasRouteOptions {
1415
1491
  */
1416
1492
  wrap?: (children: ReactNode, ctx: CanvasWrapContext) => ReactNode;
1417
1493
  /**
1418
- * Tagged data cache (instant publish). Default: enabled, scoped to the
1419
- * kit's content scope so the studio's webhook tags match. `false` disables
1420
- * caching entirely; an explicit `CanvasCacheOptions` overrides the scope.
1421
- * The kit's {@link ElytraHost.contentMode} is always part of the entry key
1422
- * (EC-543) — an override can replace the scope, never drop the mode.
1494
+ * Tagged data cache (instant publish). Always on and always scoped to the kit's
1495
+ * {@link ElytraHost.contentScope}, with the kit's
1496
+ * {@link ElytraHost.contentMode} in the entry key (EC-543). `false` disables
1497
+ * caching entirely.
1498
+ *
1499
+ * There is no scope override (EC-579): the kit's revalidate receiver derives its
1500
+ * tags with that same `contentScope`, so one value scopes both the entries and
1501
+ * their invalidation. An override here was the one way left for the two to
1502
+ * disagree — and a disagreement drops nothing, silently.
1423
1503
  */
1424
- cache?: CanvasCacheOptions | false;
1504
+ cache?: false;
1425
1505
  }
1426
1506
  interface LoadGlobalsOptions {
1427
1507
  /** Perspective of the chrome read. Default `'published'` (live is published-only). */
@@ -1458,8 +1538,9 @@ type ElytraContentMode = 'live' | 'fallback' | 'empty';
1458
1538
  /** The delivery host handle {@link createElytraHost} yields. */
1459
1539
  interface ElytraHost {
1460
1540
  /**
1461
- * The cache-tag scope: `ELYTRA_PROJECT_ID` in live mode (matching the
1462
- * studio's webhook tags), else the config slug.
1541
+ * The cache-tag scope: `ELYTRA_PROJECT_ID` in live mode, else the config slug.
1542
+ * The ONE scope of this host (EC-579): its cache entries are written under it and
1543
+ * its revalidate receiver derives every invalidation tag with it.
1463
1544
  */
1464
1545
  contentScope: string;
1465
1546
  /**
@@ -1492,11 +1573,13 @@ interface ElytraHost {
1492
1573
  /** The catch-all canvas route pair for `app/[[...slug]]/page.tsx`. */
1493
1574
  canvasRoute(options?: HostCanvasRouteOptions): CanvasRoute;
1494
1575
  /**
1495
- * The instant-publish webhook handlers for `app/api/revalidate/route.ts`,
1496
- * wired to `REVALIDATE_SECRET` (+ `STUDIO_ORIGIN` CORS). Unset secret =
1497
- * endpoint closed (401), never a default secret.
1576
+ * The instant-publish webhook handler for `app/api/revalidate/route.ts`, wired to
1577
+ * `REVALIDATE_SECRET` and to this host's own project — {@link contentScope} and the
1578
+ * config locales — which it derives the tags of every event with (EC-579). Unset
1579
+ * secret = endpoint closed (401), never a default secret. Only the secret can be
1580
+ * overridden: the scope and locales are the ones this kit's cache entries carry.
1498
1581
  */
1499
- revalidateRoute(overrides?: Partial<RevalidateRouteOptions>): RevalidateRouteHandlers;
1582
+ revalidateRoute(overrides?: Partial<Pick<RevalidateRouteOptions, 'secret'>>): RevalidateRouteHandlers;
1500
1583
  /**
1501
1584
  * The draft-mode ENABLE route for e.g. `app/api/draft/route.ts` —
1502
1585
  * `GET /api/draft?token=…&redirect=/path`, gated by the ONE
@@ -1542,8 +1625,11 @@ interface LiveContentOptions {
1542
1625
  /** Convex deployment URL, e.g. `https://your-deployment-123.convex.cloud`. */
1543
1626
  convexUrl: string;
1544
1627
  projectId: string;
1545
- /** Project locale config — config-owned (AD-11), never read from the snapshot. */
1546
- locales: LocaleConfig;
1628
+ /**
1629
+ * Project locale config — config-owned (AD-11), never read from the snapshot.
1630
+ * The declared shape; parsed where the source is assembled (EC-580).
1631
+ */
1632
+ locales: LocaleConfigInput;
1547
1633
  /** CMS collection schema — config-owned (AD-11); drives depth-1 relation population. */
1548
1634
  collections: readonly CollectionDef[];
1549
1635
  /** Route records — config-owned (AD-3/AD-11); merged with the home fallback. */
@@ -1668,4 +1754,4 @@ declare function evaluatePreviewRequest(requestUrl: string | URL, expectedToken:
1668
1754
  */
1669
1755
  declare const PACKAGE = "@elytracms/next";
1670
1756
 
1671
- export { type AssetImageLoaderOptions, type CacheWrapper, type CacheWrapperOptions, type CachedEntry, type CanvasCacheOptions, type CanvasContentRequest, type CanvasPageOutcome, CanvasRenderer, type CanvasRendererProps, type CanvasRequestInfo, type CanvasRoute, type CanvasRouteOptions, type CanvasRouteProps, type CanvasSitemapOptions, type CanvasSitemapResult, type CanvasWrapContext, type ContentSnapshot, type ContentSnapshotOptions, type ContentSource, type CreateCanvasSitemapOptions, type DefineHostComponentsOptions, type DeliveryPresentation, ELYTRA_HOST_ENV_KEYS, type ElytraContentMode, type ElytraHost, type ElytraHostEnv, type ElytraHostImageOptions, type ElytraHostOptions, ElytraImage, type ElytraImageAsset, type ElytraImageProps, type GlobalsConventions, type HostCanvasRouteOptions, type HostComponent, type HostComponents, type LiveContentOptions, type LoadGlobalsOptions, type NextImagePrimitiveOptions, PACKAGE, type PreviewEvaluation, type PreviewRouteHandlers, type PreviewRouteOptions, REVALIDATE_SIGNATURE_HEADER, type ResolveCanvasPageOptions, type ResolveGlobalsOptions, type ResolvedLink, type ResolvedNavEntry, type ResolvedNavLink, type RevalidateEvaluation, type RevalidatePayload, type RevalidateRouteHandlers, type RevalidateRouteOptions, type SiteGlobals, type SitemapParamsProvider, type SitemapRouteContext, type SitemapSkippedRoute, type StaticContentData, assembleLiveSource, bodyItemsOf, canvasPageMetadata, canvasSitemapEntries, collectionCacheTag, createAssetImageLoader, createCanvasRoute, createCanvasSitemap, createContentSnapshot, createElytraHost, createPreviewDisableRoute, createPreviewRoute, createRevalidateRoute, createStaticContentSource, defineHostComponents, docCacheTag, elytraHostImage, evaluatePreviewRequest, evaluateRevalidateRequest, firstBlockFullBleed, loadCachedEntry, loadLiveContent, mergeCacheTags, nextCacheTags, nextImagePrimitive, pathFromSlug, projectSweepTag, resolveCanvasPage, resolveGlobals, resolveHostEnv, revalidatePayloadSchema, routeCacheTag, safeRedirectTarget, scopeCacheTag, signRevalidateBody };
1757
+ export { type AssetImageLoaderOptions, type CacheWrapper, type CacheWrapperOptions, type CachedEntry, type CanvasCacheOptions, type CanvasContentRequest, type CanvasPageOutcome, CanvasRenderer, type CanvasRendererProps, type CanvasRequestInfo, type CanvasRoute, type CanvasRouteOptions, type CanvasRouteProps, type CanvasSitemapOptions, type CanvasSitemapResult, type CanvasWrapContext, type ContentSnapshot, type ContentSnapshotOptions, type ContentSource, type CreateCanvasSitemapOptions, type DefineHostComponentsOptions, type DeliveryPresentation, ELYTRA_HOST_ENV_KEYS, type ElytraContentMode, type ElytraHost, type ElytraHostEnv, type ElytraHostImageOptions, type ElytraHostOptions, ElytraImage, type ElytraImageAsset, type ElytraImageProps, type GlobalsConventions, type HostCanvasRouteOptions, type HostComponent, type HostComponents, type LiveContentOptions, type LoadGlobalsOptions, type NextImagePrimitiveOptions, PACKAGE, type PreviewEvaluation, type PreviewRouteHandlers, type PreviewRouteOptions, type ResolveCanvasPageOptions, type ResolveGlobalsOptions, type ResolvedLink, type ResolvedNavEntry, type ResolvedNavLink, type RevalidateEvaluation, type RevalidateRouteHandlers, type RevalidateRouteOptions, type RevalidationProject, type SiteGlobals, type SitemapParamsProvider, type SitemapRouteContext, type SitemapSkippedRoute, type StaticContentData, assembleLiveSource, bodyItemsOf, canvasPageMetadata, canvasSitemapEntries, collectionCacheTag, createAssetImageLoader, createCanvasRoute, createCanvasSitemap, createContentSnapshot, createElytraHost, createPreviewDisableRoute, createPreviewRoute, createRevalidateRoute, createStaticContentSource, defineHostComponents, docCacheTag, elytraHostImage, evaluatePreviewRequest, evaluateRevalidateRequest, firstBlockFullBleed, loadCachedEntry, loadLiveContent, mergeCacheTags, mergeMetadataOverParent, nextCacheTags, nextImagePrimitive, openGraphLocale, pathFromSlug, projectSweepTag, resolveCanvasPage, resolveGlobals, resolveHostEnv, revalidationTags, routeCacheTag, safeRedirectTarget, scopeCacheTag };