@riverbankcms/sdk 0.135.0 → 0.136.0

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 (153) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +4 -8
  3. package/dist/{PreviewEditorSidebar-roNY0GVy.mjs → PreviewEditorSidebar-BU5ZlhQR.mjs} +4 -4
  4. package/dist/{PreviewEditorUI-KboDbEh_.mjs → PreviewEditorUI-9XkHL-bC.mjs} +3 -3
  5. package/dist/{PreviewQueryProvider-DcgaESkm.mjs → PreviewQueryProvider-wbEpXryy.mjs} +1 -1
  6. package/dist/{SdkPreviewModeRuntime-DBrF7Caa.mjs → SdkPreviewModeRuntime-CnWmj-2B.mjs} +50 -99
  7. package/dist/{SiteChromeCustomizeContext-DrIEpJKn.mjs → SiteChromeCustomizeContext-B6d5zkog.mjs} +2334 -1173
  8. package/dist/_dts/ai/src/contracts/proposals.d.ts +8 -554
  9. package/dist/_dts/ai/src/contracts/siteStyleProposalSchemas.d.ts +401 -2312
  10. package/dist/_dts/api/src/contentRuntime.d.ts +6 -0
  11. package/dist/_dts/api/src/endpoints.d.ts +7 -0
  12. package/dist/_dts/api/src/index.d.ts +1 -0
  13. package/dist/_dts/api/src/publicEventEndpointDefinitions.d.ts +7 -0
  14. package/dist/_dts/api/src/publicExperience/eventEndpoints.d.ts +26 -0
  15. package/dist/_dts/api/src/sdkBlockContentAdmission.d.ts +5 -0
  16. package/dist/_dts/api/src/sdkThemeWire.d.ts +6 -432
  17. package/dist/_dts/block-form/src/blockDraftSaveability.d.ts +2 -3
  18. package/dist/_dts/block-form/src/utils/slug.d.ts +6 -0
  19. package/dist/_dts/blocks/src/PageRenderer.d.ts +12 -6
  20. package/dist/_dts/blocks/src/contracts/proof-block-islands.d.ts +48 -0
  21. package/dist/_dts/blocks/src/definitions.d.ts +14 -0
  22. package/dist/_dts/blocks/src/index.d.ts +4 -3
  23. package/dist/_dts/blocks/src/pageRenderAdmission.d.ts +40 -0
  24. package/dist/_dts/blocks/src/server.d.ts +2 -2
  25. package/dist/_dts/blocks/src/system/blocks/events/shared/schemas.d.ts +25 -0
  26. package/dist/_dts/blocks/src/system/data/index.d.ts +2 -0
  27. package/dist/_dts/blocks/src/system/data/publicLoaderCachePolicy.d.ts +78 -0
  28. package/dist/_dts/blocks/src/system/manifest/fieldValidation/index.d.ts +2 -18
  29. package/dist/_dts/blocks/src/system/manifest/validation.d.ts +103 -16
  30. package/dist/_dts/blocks/src/system/node/validation-constants.d.ts +1 -1
  31. package/dist/_dts/blocks/src/system/runtime/api/client.d.ts +2 -1
  32. package/dist/_dts/blocks/src/system/runtime/api/endpoints.d.ts +8 -0
  33. package/dist/_dts/blocks/src/system/runtime/api/index.d.ts +1 -1
  34. package/dist/_dts/blocks/src/system/runtime/api/types/events.d.ts +19 -0
  35. package/dist/_dts/blocks/src/system/runtime/components/registry.client.d.ts +52 -0
  36. package/dist/_dts/blocks/src/system/runtime/components/runtime-backed-blocks.d.ts +24 -0
  37. package/dist/_dts/blocks/src/system/runtime/hooks/useCalendarEvents.d.ts +44 -18
  38. package/dist/_dts/blocks/src/system/runtime/hooks/useCalendarEvents.state.d.ts +20 -3
  39. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/VerifyingTimeoutStep.d.ts +3 -1
  40. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationStripeReturn.d.ts +1 -0
  41. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventCalendar.client.d.ts +2 -2
  42. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventCalendar.interactive.d.ts +40 -0
  43. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventCombined.client.d.ts +2 -0
  44. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventCombined.interactive.d.ts +43 -0
  45. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/CalendarDayCell.d.ts +3 -1
  46. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/CalendarNavigationChevron.d.ts +4 -0
  47. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/DayEventsModal.d.ts +2 -1
  48. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/EventModalContext.d.ts +3 -2
  49. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekAgendaView.d.ts +3 -0
  50. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekBoardView.d.ts +31 -3
  51. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekTimetableView.d.ts +3 -1
  52. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCollectionContract.d.ts +19 -1
  53. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/timetableModel.d.ts +4 -3
  54. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/utils.d.ts +3 -3
  55. package/dist/_dts/blocks/src/system/transforms/registry/layout.d.ts +0 -1
  56. package/dist/_dts/calendar/src/grid.d.ts +24 -66
  57. package/dist/_dts/calendar/src/index.d.ts +2 -2
  58. package/dist/_dts/calendar/src/timetable.d.ts +5 -9
  59. package/dist/_dts/commerce/src/purchase/allocation.d.ts +11 -0
  60. package/dist/_dts/commerce/src/purchase/index.d.ts +2 -0
  61. package/dist/_dts/commerce/src/purchase/settlement.d.ts +33 -0
  62. package/dist/_dts/commerce/src/purchase/stripeCheckoutReturnUrl.d.ts +8 -0
  63. package/dist/_dts/content-model/src/templates/defaults.d.ts +6 -0
  64. package/dist/_dts/core/src/slug.d.ts +20 -5
  65. package/dist/_dts/preview-next/src/client/blocks/EditablePageRenderer.d.ts +1 -1
  66. package/dist/_dts/preview-next/src/client/preview/headerInteractionPaletteSurfaceState.d.ts +2 -1
  67. package/dist/_dts/sdk/src/client/publicLoaderCacheAccess.d.ts +6 -0
  68. package/dist/_dts/sdk/src/public-api/contracts.d.ts +5 -1
  69. package/dist/_dts/sdk/src/rendering/blocks-page-renderer.d.ts +10 -6
  70. package/dist/_dts/sdk/src/rendering/components/Block.shared.d.ts +0 -1
  71. package/dist/_dts/sdk/src/rendering/components/pageShared.d.ts +4 -4
  72. package/dist/_dts/sdk/src/rendering/helpers/footerPageOutline.d.ts +3 -0
  73. package/dist/_dts/sdk/src/rendering/helpers/loadContent.d.ts +3 -0
  74. package/dist/_dts/sdk/src/rendering/helpers/loadPage.d.ts +1 -1
  75. package/dist/_dts/sdk/src/rendering/helpers/pageRuntimeSupport.d.ts +17 -0
  76. package/dist/_dts/sdk/src/rendering/helpers/templateContent.d.ts +9 -3
  77. package/dist/_dts/sdk/src/rendering/hooks/usePage.d.ts +1 -1
  78. package/dist/_dts/sdk/src/rendering/index.d.ts +2 -2
  79. package/dist/_dts/sdk/src/rendering/overrideResolution.d.ts +7 -4
  80. package/dist/_dts/sdk/src/version.d.ts +1 -1
  81. package/dist/_dts/theme-core/src/cards/types.d.ts +8 -8
  82. package/dist/_dts/theme-core/src/data.d.ts +12 -0
  83. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  84. package/dist/_dts/theme-core/src/header/legacyHeaderVariant.d.ts +2 -6
  85. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +6 -807
  86. package/dist/_dts/theme-core/src/schema.d.ts +452 -1278
  87. package/dist/_dts/theme-core/src/site-styles/curatedSiteStyles.d.ts +213 -1
  88. package/dist/_dts/theme-core/src/site-styles/headerLayoutCatalog.d.ts +0 -29
  89. package/dist/_dts/theme-core/src/site-styles/headerLayoutStyles.d.ts +10 -0
  90. package/dist/_dts/theme-core/src/site-styles/headerLayoutTypes.d.ts +8 -5
  91. package/dist/_dts/theme-core/src/site-styles/headerOpeningForeground.d.ts +4 -4
  92. package/dist/_dts/theme-core/src/site-styles/headerStyleCompiler.d.ts +16 -2
  93. package/dist/_dts/theme-core/src/site-styles/headerStyleDefaults.d.ts +465 -0
  94. package/dist/_dts/theme-core/src/site-styles/headerStyleRecipe.d.ts +161 -0
  95. package/dist/client/{BookingConsentAgreement-BQqwi8Oa.mjs → BookingConsentAgreement-DCch21a6.mjs} +2 -2
  96. package/dist/client/{DiscountCodeDisclosure-B97aMo0V.mjs → DiscountCodeDisclosure-DIj1T59K.mjs} +1 -1
  97. package/dist/client/{EventCombined.client-Bs2Iw9Q6.mjs → EventCombined.client-BWVNlXTx.mjs} +226 -206
  98. package/dist/client/{EventListing.client-C4QUQdmg.mjs → EventListing.client-DVFCDe3i.mjs} +2 -2
  99. package/dist/client/{LeafletStaticMap-CwWRk69M.mjs → LeafletStaticMap-BY5mIHw9.mjs} +1 -1
  100. package/dist/client/{accordion-BA0j8Mpw.mjs → accordion-CK29E8rG.mjs} +1 -1
  101. package/dist/client/{bookingForm-Br2e41H0.mjs → bookingForm-DFvnet-N.mjs} +5 -5
  102. package/dist/client/bookings.mjs +87 -47
  103. package/dist/client/{carousel-B4dmwnpD.mjs → carousel-BVU5NQlb.mjs} +1 -1
  104. package/dist/client/{cart-B_ckGW4b.mjs → cart-BNItcZVa.mjs} +3 -3
  105. package/dist/client/{checkout-DAyspHdf.mjs → checkout-Bu6iCdkP.mjs} +3 -3
  106. package/dist/client/client.mjs +1985 -1055
  107. package/dist/client/{context-DljQn8L5.mjs → context-BImarOAq.mjs} +12 -0
  108. package/dist/client/{eventCalendar-pJu3FUod.mjs → eventCalendar-DS2h-UXD.mjs} +256 -114
  109. package/dist/client/{eventCombined-B6Kacm9F.mjs → eventCombined-Drt4Oq9p.mjs} +3 -3
  110. package/dist/client/{eventDetails-CaX9z1pv.mjs → eventDetails-Bl2vWgTX.mjs} +4 -3
  111. package/dist/client/{eventListing-B9aXPO-I.mjs → eventListing-CS7PHY7v.mjs} +3 -3
  112. package/dist/client/{eventRegistration-CBSovBkg.mjs → eventRegistration-D-j8eNy2.mjs} +85 -41
  113. package/dist/client/{form-Cm0tu_7o.mjs → form-RMpoLTyr.mjs} +2 -2
  114. package/dist/client/{headerSection-OgRtHeKS.mjs → headerSection-BYE3Lb3k.mjs} +2 -2
  115. package/dist/client/hooks.mjs +2254 -298
  116. package/dist/client/{locationMap-DmjcYqNM.mjs → locationMap-BxSr7Kkh.mjs} +2 -2
  117. package/dist/client/{newsletterForm-D08OZQAC.mjs → newsletterForm-DerbXwFC.mjs} +2 -2
  118. package/dist/client/{productDetail-BqZeu2P4.mjs → productDetail-4SZCLhAf.mjs} +3 -3
  119. package/dist/client/{productList-mNyIF9FG.mjs → productList-D7AP4GNq.mjs} +3 -3
  120. package/dist/client/{propsDecoder-DwHXRm9X.mjs → propsDecoder-pzBeCh14.mjs} +123 -46
  121. package/dist/client/rendering/client.mjs +793 -488
  122. package/dist/client/rendering/islands.mjs +17 -17
  123. package/dist/client/rendering.mjs +2161 -1268
  124. package/dist/client/{scrollToBlock-DtmdM3v-.mjs → scrollToBlock-CduAKf8T.mjs} +1 -1
  125. package/dist/client/{shop-CNjZK16F.mjs → shop-Cp5p_kGe.mjs} +6 -6
  126. package/dist/client/{shop-commerce-DkNNao1J.mjs → shop-commerce-DicXYaYX.mjs} +5 -5
  127. package/dist/client/{shop-commerce-store-9WA--zuW.mjs → shop-commerce-store-CGWnpY4K.mjs} +1 -1
  128. package/dist/client/{shopShared-CHCVfccY.mjs → shopShared-BDf9ljkm.mjs} +3 -3
  129. package/dist/client/{useEventFilters-DvuaB4ZN.mjs → useEventFilters-DeeyGYuk.mjs} +3 -3
  130. package/dist/preview-next/before-render.mjs +7 -0
  131. package/dist/preview-next/client/runtime.mjs +4 -4
  132. package/dist/preview-next/keepAlive.mjs +7 -0
  133. package/dist/preview-next/middleware.mjs +7 -0
  134. package/dist/{previewAuthStore-C7ksxt5b.mjs → previewAuthStore-DbqBol0V.mjs} +7 -0
  135. package/dist/{sdk-runtime-3GhW4knb.mjs → sdk-runtime-19MO27F0.mjs} +2 -2
  136. package/dist/{separator-D3bCWYYb.mjs → separator-DaPLtJ1f.mjs} +1 -1
  137. package/dist/server/components.mjs +1562 -917
  138. package/dist/server/config-validation.mjs +94 -135
  139. package/dist/server/config.mjs +94 -135
  140. package/dist/server/data.mjs +94 -134
  141. package/dist/server/index.mjs +122 -4
  142. package/dist/server/manifest.mjs +0 -1
  143. package/dist/server/next.mjs +1467 -732
  144. package/dist/server/page-converter.mjs +87 -113
  145. package/dist/server/prebuild.mjs +1 -1
  146. package/dist/server/rendering/server.mjs +1717 -939
  147. package/dist/server/rendering.mjs +1729 -1044
  148. package/dist/server/routing.mjs +3229 -1835
  149. package/dist/server/server.mjs +21739 -19801
  150. package/dist/server/theme-bridge.mjs +656 -264
  151. package/dist/server/theme.mjs +2 -17
  152. package/dist/styles/index.css +609 -181
  153. package/package.json +1 -1
@@ -1200,6 +1200,13 @@ const PUBLIC_WHOLE_SERIES_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1200
1200
  //#endregion
1201
1201
  //#region ../api/src/publicEventEndpointDefinitions.ts
1202
1202
  const PUBLIC_EVENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1203
+ resolvePublicEventCheckoutReturn: {
1204
+ path: "/public/sites/{siteId}/events/checkout-return",
1205
+ method: "POST",
1206
+ tags: [TAG_TEMPLATES.publicEvents, "event-checkout"],
1207
+ auth: "public",
1208
+ responseKind: "json"
1209
+ },
1203
1210
  getPublicEventRegistrationOptions: {
1204
1211
  path: "/public/sites/{siteId}/events/registration-options",
1205
1212
  method: "POST",
@@ -1709,7 +1716,7 @@ const parseUserId = (value) => parseUuidBrand(value, unsafeAsUserId);
1709
1716
  const parseEventSeriesId = (value) => parseUuidBrand(value, unsafeAsEventSeriesId);
1710
1717
  //#endregion
1711
1718
  //#region ../core/src/assert-never.ts
1712
- function assertNever$2(value, message = "Unexpected variant") {
1719
+ function assertNever$3(value, message = "Unexpected variant") {
1713
1720
  throw new Error(`${message}: ${String(value)}`);
1714
1721
  }
1715
1722
  //#endregion
@@ -1798,7 +1805,7 @@ function createEventSeriesPageSurfaceSubject(eventSeriesId, bookingScope) {
1798
1805
  };
1799
1806
  }
1800
1807
  function parseEventPageSurfaceSubject(value) {
1801
- if (!isRecord$10(value)) return err({ kind: "notObject" });
1808
+ if (!isRecord$14(value)) return err({ kind: "notObject" });
1802
1809
  switch (value.kind) {
1803
1810
  case "eventSeries": {
1804
1811
  const eventSeriesId = parseEventSeriesId(value.eventSeriesId);
@@ -1843,7 +1850,7 @@ function parseEventPageSurfaceSubject(value) {
1843
1850
  });
1844
1851
  }
1845
1852
  }
1846
- function isRecord$10(value) {
1853
+ function isRecord$14(value) {
1847
1854
  return typeof value === "object" && value !== null && !Array.isArray(value);
1848
1855
  }
1849
1856
  //#endregion
@@ -1931,6 +1938,9 @@ function getZonedDateTimeParts(date, timeZone) {
1931
1938
  function formatDateKey(date) {
1932
1939
  return `${String(date.year).padStart(4, "0")}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}`;
1933
1940
  }
1941
+ function toBookingTimeZone(timeZone) {
1942
+ return getSafeTimeZone(timeZone);
1943
+ }
1934
1944
  function parseUtcIsoDateTimeString(value) {
1935
1945
  const normalized = value.trim();
1936
1946
  const hasUtcSuffix = normalized.endsWith("Z");
@@ -2438,7 +2448,7 @@ const entryLinkSchema$1 = z.object({
2438
2448
  contentType: z.string().min(1),
2439
2449
  identifier: z.string().min(1)
2440
2450
  }).passthrough();
2441
- z.union([
2451
+ const linkSchema = z.union([
2442
2452
  internalLinkSchema,
2443
2453
  externalLinkSchema$1,
2444
2454
  customLinkSchema$1,
@@ -8889,7 +8899,7 @@ function asDesignSystemTemplateId(value) {
8889
8899
  function makeBoundaryId(previousBlockId, nextBlockId) {
8890
8900
  return asDesignBoundaryId(`boundary:${encodeURIComponent(previousBlockId)}:${encodeURIComponent(nextBlockId)}`);
8891
8901
  }
8892
- function assertNever$1(value, message = "Unhandled Theme V2 variant") {
8902
+ function assertNever$2(value, message = "Unhandled Theme V2 variant") {
8893
8903
  throw new Error(`${message}: ${String(value)}`);
8894
8904
  }
8895
8905
  function brandNonEmptyString(value, label) {
@@ -9146,12 +9156,8 @@ const legacyHeaderVariantValues = Object.keys(legacyHeaderVariants);
9146
9156
  * `@riverbankcms/api` is `z.input<typeof themeSchema>`, so a widened input here
9147
9157
  * would leak straight into a public type.
9148
9158
  *
9149
- * Not every read of a persisted theme goes through the schema:
9150
- * `resolveRuntimeTheme` in
9151
- * `apps/dashboard/src/server/services/sites/runtimeSiteResponse.ts` returns the
9152
- * raw database JSON asserted as `Theme` with no parse, so a theme still
9153
- * carrying a legacy spelling reaches the theme editor un-aliased. That gap
9154
- * predates this alias and is harmless while persisted usage is zero.
9159
+ * Persisted runtime themes are admitted through `parseThemeForPersistence`
9160
+ * before reaching consumers, so legacy spellings normalize at that boundary.
9155
9161
  */
9156
9162
  const headerVariantWithLegacyAliasSchema = z.enum([...headerVariantValues, ...legacyHeaderVariantValues]).transform((value) => value in legacyHeaderVariants ? legacyHeaderVariants[value] : value);
9157
9163
  const BREAKPOINT_LG = 1024;
@@ -9265,7 +9271,7 @@ const boxBackgroundSchema = z.discriminatedUnion("type", [
9265
9271
  })
9266
9272
  ]);
9267
9273
  function normalizeAuthoringOpacity(value) {
9268
- if (!isRecord$9(value)) return value;
9274
+ if (!isRecord$13(value)) return value;
9269
9275
  return {
9270
9276
  ...value,
9271
9277
  opacity: normalizeOpacity(value.opacity)
@@ -9274,7 +9280,7 @@ function normalizeAuthoringOpacity(value) {
9274
9280
  function normalizeOpacity(value) {
9275
9281
  return typeof value === "number" && value > 1 && value <= 100 ? value / 100 : value;
9276
9282
  }
9277
- function isRecord$9(value) {
9283
+ function isRecord$13(value) {
9278
9284
  return typeof value === "object" && value !== null && !Array.isArray(value);
9279
9285
  }
9280
9286
  //#endregion
@@ -9610,7 +9616,6 @@ const navFontWeight = z.enum([
9610
9616
  "bold"
9611
9617
  ]);
9612
9618
  const headerMaxWidth = z.enum(["container", "full"]);
9613
- const headerBarFrame = z.enum(["full-bleed", "inset-shell"]);
9614
9619
  const headerBorderSchema = componentBorderSchema.extend({ position: z.enum([
9615
9620
  "bottom",
9616
9621
  "top",
@@ -9639,12 +9644,6 @@ const headerLogoTextSchema = z.object({
9639
9644
  ]).optional(),
9640
9645
  hideOnShrink: z.boolean().optional()
9641
9646
  }).optional();
9642
- const navEffectsSchema = z.object({
9643
- underlineGradient: z.boolean().default(false),
9644
- glow: z.boolean().default(false),
9645
- glowColor: z.string().optional(),
9646
- neumorphic: z.boolean().default(false)
9647
- }).optional();
9648
9647
  const dropdownStyleSchema = z.object({
9649
9648
  background: themeColorTokenSchema.default("surface"),
9650
9649
  textColor: themeColorTokenSchema.default("text"),
@@ -9736,9 +9735,7 @@ const headerSchema = z.object({
9736
9735
  positioning: headerPositioning,
9737
9736
  shrinkOnScroll: z.boolean(),
9738
9737
  maxWidth: headerMaxWidth,
9739
- barFrame: headerBarFrame.optional(),
9740
9738
  scrollTreatment: headerScrollTreatmentSchema,
9741
- logoOverride: mediaSchema.nullable().optional(),
9742
9739
  background: headerBackgroundSchema.default({
9743
9740
  type: "color",
9744
9741
  color: "surface"
@@ -9750,13 +9747,11 @@ const headerSchema = z.object({
9750
9747
  navFontRole: z.enum(["body", "heading"]).optional(),
9751
9748
  navTextSize: navLinkStyleSchema.shape.textSize,
9752
9749
  interactionPalette: headerInteractionPaletteSchema.optional(),
9753
- navPlacement: z.enum(["end", "center"]).optional(),
9754
9750
  container: headerContainerSchema,
9755
9751
  border: headerBorderSchema,
9756
9752
  shadow: componentShadowSchema.optional(),
9757
9753
  logo: logoStyleSchema,
9758
9754
  logoText: headerLogoTextSchema,
9759
- navEffects: navEffectsSchema,
9760
9755
  navContainer: navContainerSchema,
9761
9756
  navLinkStyle: navLinkStyleSchema.optional(),
9762
9757
  ctaGap: headerCtaGapSchema.optional(),
@@ -11851,27 +11846,6 @@ function asHeaderStyleId(value) {
11851
11846
  return value;
11852
11847
  }
11853
11848
  //#endregion
11854
- //#region ../theme-core/src/site-styles/headerInteractionPalette.ts
11855
- const clearSplitInteractionPalette = defineLookOptionGroup({
11856
- id: "interaction-palette",
11857
- label: "Hover colour",
11858
- defaultOptionId: "accent",
11859
- options: [
11860
- {
11861
- id: "accent",
11862
- label: "Accent"
11863
- },
11864
- {
11865
- id: "primary",
11866
- label: "Primary"
11867
- },
11868
- {
11869
- id: "secondary",
11870
- label: "Secondary"
11871
- }
11872
- ]
11873
- });
11874
- //#endregion
11875
11849
  //#region ../theme-core/src/site-styles/headerLayoutCatalog.ts
11876
11850
  const titleOnlyLogoAccommodation = {
11877
11851
  pattern: "title-only",
@@ -12031,43 +12005,37 @@ const headerStyleCatalog = defineExactCatalog()([
12031
12005
  id: asHeaderStyleId("minimal"),
12032
12006
  label: "Minimal",
12033
12007
  description: "Quiet surface with simple color-change navigation.",
12034
- supportedLayoutIds: supportedLayouts(["classic", "centered"]),
12035
- navTypography: "plain"
12008
+ supportedLayoutIds: supportedLayouts(["classic", "centered"])
12036
12009
  },
12037
12010
  {
12038
12011
  id: asHeaderStyleId("fine-line"),
12039
12012
  label: "Seamless",
12040
12013
  description: "Borderless surface with underline hover for a quieter page transition.",
12041
- supportedLayoutIds: supportedLayouts(["classic", "centered"]),
12042
- navTypography: "plain"
12014
+ supportedLayoutIds: supportedLayouts(["classic", "centered"])
12043
12015
  },
12044
12016
  {
12045
12017
  id: asHeaderStyleId("soft-bar"),
12046
12018
  label: "Soft Bar",
12047
12019
  description: "Soft tinted surface with gentle pill hover states.",
12048
- supportedLayoutIds: supportedLayouts(["classic", "centered"]),
12049
- navTypography: "plain"
12020
+ supportedLayoutIds: supportedLayouts(["classic", "centered"])
12050
12021
  },
12051
12022
  {
12052
12023
  id: asHeaderStyleId("pill-nav"),
12053
12024
  label: "Pill Nav",
12054
12025
  description: "A calm pill navigation rail over a neutral header.",
12055
- supportedLayoutIds: supportedLayouts(["classic", "centered"]),
12056
- navTypography: "plain"
12026
+ supportedLayoutIds: supportedLayouts(["classic", "centered"])
12057
12027
  },
12058
12028
  {
12059
12029
  id: asHeaderStyleId("brand-bar"),
12060
12030
  label: "Brand Bar",
12061
12031
  description: "Solid brand surface with high-contrast navigation.",
12062
- supportedLayoutIds: supportedLayouts(["classic", "centered"]),
12063
- navTypography: "brand-caps"
12032
+ supportedLayoutIds: supportedLayouts(["classic", "centered"])
12064
12033
  },
12065
12034
  {
12066
12035
  id: asHeaderStyleId("brand-underline"),
12067
12036
  label: "Brand Underline",
12068
12037
  description: "Brand surface with underline-grow navigation.",
12069
- supportedLayoutIds: supportedLayouts(["classic"]),
12070
- navTypography: "brand-caps"
12038
+ supportedLayoutIds: supportedLayouts(["classic"])
12071
12039
  },
12072
12040
  {
12073
12041
  id: asHeaderStyleId("brand-pill"),
@@ -12077,73 +12045,78 @@ const headerStyleCatalog = defineExactCatalog()([
12077
12045
  "classic",
12078
12046
  "centered",
12079
12047
  "floating"
12080
- ]),
12081
- navTypography: "brand-caps"
12048
+ ])
12082
12049
  },
12083
12050
  {
12084
12051
  id: asHeaderStyleId("brand-capsule"),
12085
12052
  label: "Brand Capsule",
12086
12053
  description: "Solid brand chrome designed as a floating capsule, with quiet-caps nav, a soft pill active state, and an inverted CTA pill.",
12087
- supportedLayoutIds: supportedLayouts(["classic"]),
12088
- navTypography: "quiet-caps"
12054
+ supportedLayoutIds: supportedLayouts(["classic"])
12089
12055
  },
12090
12056
  {
12091
12057
  id: asHeaderStyleId("serif-line"),
12092
12058
  label: "Serif Line",
12093
12059
  description: "Typographic logo treatment with understated underline navigation.",
12094
- supportedLayoutIds: supportedLayouts(["classic", "centered"]),
12095
- navTypography: "heading"
12060
+ supportedLayoutIds: supportedLayouts(["classic", "centered"])
12096
12061
  },
12097
12062
  {
12098
12063
  id: asHeaderStyleId("flat-tabs"),
12099
12064
  label: "Flat Tabs",
12100
12065
  description: "Flat block hover and active states with a Swiss utility feel.",
12101
- supportedLayoutIds: supportedLayouts(["classic", "centered"]),
12102
- navTypography: "quiet-caps"
12066
+ supportedLayoutIds: supportedLayouts(["classic", "centered"])
12103
12067
  },
12104
12068
  {
12105
12069
  id: asHeaderStyleId("glass"),
12106
12070
  label: "Glass",
12107
12071
  description: "Translucent chrome with frosted navigation backing.",
12108
- supportedLayoutIds: supportedLayouts(["floating"]),
12109
- navTypography: "plain"
12072
+ supportedLayoutIds: supportedLayouts(["floating"])
12110
12073
  },
12111
12074
  {
12112
12075
  id: asHeaderStyleId("minimal-surface"),
12113
12076
  label: "Clear Split",
12114
12077
  description: "Floating logo with transparent navigation and no shared shell.",
12115
- supportedLayoutIds: supportedLayouts(["floating"]),
12116
- navTypography: "plain",
12117
- openingForegroundTarget: "all",
12118
- scrollTreatment: "contrast-glass",
12119
- interactionPalette: clearSplitInteractionPalette
12078
+ supportedLayoutIds: supportedLayouts(["floating"])
12120
12079
  },
12121
12080
  {
12122
12081
  id: asHeaderStyleId("soft-shell"),
12123
12082
  label: "Soft Shell",
12124
12083
  description: "Floating rounded shell with soft pill navigation states.",
12125
- supportedLayoutIds: supportedLayouts(["floating"]),
12126
- navTypography: "plain"
12084
+ supportedLayoutIds: supportedLayouts(["floating"])
12127
12085
  },
12128
12086
  {
12129
12087
  id: asHeaderStyleId("split-glass"),
12130
12088
  label: "Split Glass",
12131
12089
  description: "Logo floats independently while the navigation sits in a glass rail.",
12132
- supportedLayoutIds: supportedLayouts(["floating"]),
12133
- navTypography: "quiet-caps",
12134
- openingForegroundTarget: "brand-only"
12090
+ supportedLayoutIds: supportedLayouts(["floating"])
12135
12091
  },
12136
12092
  {
12137
12093
  id: asHeaderStyleId("split-pill"),
12138
12094
  label: "Split Pill",
12139
12095
  description: "Logo floats independently with a soft pill-backed navigation rail.",
12140
- supportedLayoutIds: supportedLayouts(["floating"]),
12141
- navTypography: "heading",
12142
- openingForegroundTarget: "brand-only"
12096
+ supportedLayoutIds: supportedLayouts(["floating"])
12143
12097
  }
12144
12098
  ]);
12145
12099
  new Map(headerLayoutCatalog.map((layout) => [layout.id, layout]));
12146
12100
  new Map(headerStyleCatalog.map((style) => [style.id, style]));
12101
+ defineLookOptionGroup({
12102
+ id: "interaction-palette",
12103
+ label: "Hover colour",
12104
+ defaultOptionId: "accent",
12105
+ options: [
12106
+ {
12107
+ id: "accent",
12108
+ label: "Accent"
12109
+ },
12110
+ {
12111
+ id: "primary",
12112
+ label: "Primary"
12113
+ },
12114
+ {
12115
+ id: "secondary",
12116
+ label: "Secondary"
12117
+ }
12118
+ ]
12119
+ });
12147
12120
  //#endregion
12148
12121
  //#region ../theme-core/src/site-styles/footerLooks.ts
12149
12122
  function asFooterLookId(value) {
@@ -14829,7 +14802,7 @@ function planSiteChromePlacement(positioning) {
14829
14802
  headerRootClassName: "rb-fixed rb-top-0 rb-left-0 rb-right-0",
14830
14803
  standaloneShellClassName: "fixed left-0 right-0 top-0 z-40 w-full"
14831
14804
  };
14832
- default: return assertNever$1(positioning);
14805
+ default: return assertNever$2(positioning);
14833
14806
  }
14834
14807
  }
14835
14808
  //#endregion
@@ -14896,14 +14869,9 @@ function formatKeyAsLabel(key) {
14896
14869
  return key.replace(/([A-Z])/g, " $1").replace(/[-_]/g, " ").trim().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
14897
14870
  }
14898
14871
  //#endregion
14899
- //#region ../blocks/src/system/manifest/registry.ts
14900
- const REGISTRY_SYMBOL$1 = Symbol.for("@riverbankcms/blocks/manifest-registry");
14901
- const isBlocksTestRuntime$1 = typeof process !== "undefined" && typeof process.env !== "undefined" && process.env.RIVERBANK_BLOCKS_TEST === "1";
14902
- const globalScope$1 = globalThis;
14903
- const manifestStore = isBlocksTestRuntime$1 ? /* @__PURE__ */ new Map() : globalScope$1[REGISTRY_SYMBOL$1] ?? (globalScope$1[REGISTRY_SYMBOL$1] = /* @__PURE__ */ new Map());
14904
- function registerManifest(manifest) {
14905
- manifestStore.set(manifest.name, manifest);
14906
- return manifest;
14872
+ //#region ../media-core/src/typeGuards.ts
14873
+ function isRecord$12(value) {
14874
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
14907
14875
  }
14908
14876
  //#endregion
14909
14877
  //#region ../media-core/src/transformRect.ts
@@ -14997,6 +14965,37 @@ function rotateNormalizedPoint(point, rotation) {
14997
14965
  }
14998
14966
  }
14999
14967
  //#endregion
14968
+ //#region ../media-core/src/mediaRefNormalizer.ts
14969
+ function looksLikeUuid(value) {
14970
+ return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
14971
+ }
14972
+ /**
14973
+ * Strip environment-specific keys from a CMS media object.
14974
+ * @mutates Mutates the passed object in-place.
14975
+ */
14976
+ function deleteEnvSpecificKeys(obj) {
14977
+ delete obj.assetId;
14978
+ delete obj.storageBucket;
14979
+ delete obj.storagePath;
14980
+ delete obj.src;
14981
+ delete obj.mimeType;
14982
+ if (looksLikeUuid(obj.id)) delete obj.id;
14983
+ }
14984
+ //#endregion
14985
+ //#region ../media-core/src/richTextImageCollector.ts
14986
+ function walkAndCollectImageAttrs(value, out, location = "") {
14987
+ if (Array.isArray(value)) {
14988
+ value.forEach((item, index) => walkAndCollectImageAttrs(item, out, `${location}[${index}]`));
14989
+ return;
14990
+ }
14991
+ if (!isRecord$12(value)) return;
14992
+ if (value.type === "image" && isRecord$12(value.attrs)) out.push({
14993
+ attrs: value.attrs,
14994
+ location: location ? `${location}.attrs` : "attrs"
14995
+ });
14996
+ for (const [key, child] of Object.entries(value)) walkAndCollectImageAttrs(child, out, location ? `${location}.${key}` : key);
14997
+ }
14998
+ //#endregion
15000
14999
  //#region ../blocks/src/system/node/schema.ts
15001
15000
  const transformStepSchema = z.object({
15002
15001
  id: z.string().min(1, "Transform requires an identifier"),
@@ -15644,6 +15643,14 @@ function parseFieldDefinition(input) {
15644
15643
  function parseFieldDefinitions(input) {
15645
15644
  return fieldSchema.array().parse(input);
15646
15645
  }
15646
+ /**
15647
+ * Normalize legacy select fields into the closed source union before any consumer
15648
+ * branches on source semantics. Runtime-backed definitions must be explicit;
15649
+ * legacy static definitions normalize to `kind: "static"`.
15650
+ */
15651
+ function normalizeSelectSource(field) {
15652
+ return field.source ?? { kind: "static" };
15653
+ }
15647
15654
  function expectParsedSelectField(field) {
15648
15655
  if (field.type !== "select") throw new Error(`Expected select field, received ${field.type}`);
15649
15656
  return field;
@@ -15802,6 +15809,1932 @@ z.object({
15802
15809
  tags: z.array(z.string()).optional(),
15803
15810
  icon: z.string().optional()
15804
15811
  });
15812
+ const inlineTextMarkSchema = z.discriminatedUnion("kind", [
15813
+ z.object({ kind: z.literal("strong") }),
15814
+ z.object({ kind: z.literal("emphasis") }),
15815
+ z.object({
15816
+ kind: z.literal("accent"),
15817
+ tone: z.enum([
15818
+ "accent",
15819
+ "brand",
15820
+ "contrast"
15821
+ ])
15822
+ }),
15823
+ z.object({
15824
+ kind: z.literal("highlight"),
15825
+ tone: z.enum([
15826
+ "soft",
15827
+ "accent",
15828
+ "brand"
15829
+ ])
15830
+ })
15831
+ ]);
15832
+ const inlineTextRunSchema = z.object({
15833
+ text: z.string(),
15834
+ marks: z.array(inlineTextMarkSchema).optional()
15835
+ });
15836
+ z.array(inlineTextRunSchema);
15837
+ const MARK_ORDER = {
15838
+ strong: 0,
15839
+ emphasis: 1,
15840
+ accent: 2,
15841
+ highlight: 3
15842
+ };
15843
+ function plainTextToInlineText(value) {
15844
+ return normalizeInlineText(value.length > 0 ? [{ text: value }] : []);
15845
+ }
15846
+ function inlineTextToPlainText(value) {
15847
+ return value.map((run) => run.text).join("");
15848
+ }
15849
+ function parseInlineTextFromUnknown(value) {
15850
+ if (!Array.isArray(value)) return {
15851
+ ok: false,
15852
+ error: { kind: "not-array" }
15853
+ };
15854
+ const runs = [];
15855
+ for (let index = 0; index < value.length; index += 1) {
15856
+ const parsed = inlineTextRunSchema.safeParse(value[index]);
15857
+ if (!parsed.success) return {
15858
+ ok: false,
15859
+ error: {
15860
+ kind: "invalid-run",
15861
+ index
15862
+ }
15863
+ };
15864
+ runs.push(parsed.data);
15865
+ }
15866
+ return {
15867
+ ok: true,
15868
+ value: normalizeInlineText(runs)
15869
+ };
15870
+ }
15871
+ function normalizeInlineText(value) {
15872
+ const normalized = [];
15873
+ for (const run of value) {
15874
+ if (run.text.length === 0) continue;
15875
+ const marks = normalizeInlineTextMarks(run.marks ?? []);
15876
+ const nextRun = marks.length > 0 ? {
15877
+ text: run.text,
15878
+ marks
15879
+ } : { text: run.text };
15880
+ const previous = normalized[normalized.length - 1];
15881
+ if (previous && inlineTextMarksEqual(previous.marks ?? [], nextRun.marks ?? [])) {
15882
+ normalized[normalized.length - 1] = previous.marks && previous.marks.length > 0 ? {
15883
+ text: previous.text + nextRun.text,
15884
+ marks: previous.marks
15885
+ } : { text: previous.text + nextRun.text };
15886
+ continue;
15887
+ }
15888
+ normalized.push(nextRun);
15889
+ }
15890
+ return normalized;
15891
+ }
15892
+ function normalizeInlineTextMarks(marks) {
15893
+ let strong = null;
15894
+ let emphasis = null;
15895
+ let accent = null;
15896
+ let highlight = null;
15897
+ for (const mark of marks) switch (mark.kind) {
15898
+ case "strong":
15899
+ strong = { kind: "strong" };
15900
+ break;
15901
+ case "emphasis":
15902
+ emphasis = { kind: "emphasis" };
15903
+ break;
15904
+ case "accent":
15905
+ accent = {
15906
+ kind: "accent",
15907
+ tone: mark.tone
15908
+ };
15909
+ break;
15910
+ case "highlight":
15911
+ highlight = {
15912
+ kind: "highlight",
15913
+ tone: mark.tone
15914
+ };
15915
+ break;
15916
+ }
15917
+ return [
15918
+ strong,
15919
+ emphasis,
15920
+ accent,
15921
+ highlight
15922
+ ].filter((mark) => mark !== null).sort((left, right) => MARK_ORDER[left.kind] - MARK_ORDER[right.kind]);
15923
+ }
15924
+ function inlineTextMarksEqual(left, right) {
15925
+ const normalizedLeft = normalizeInlineTextMarks(left);
15926
+ const normalizedRight = normalizeInlineTextMarks(right);
15927
+ if (normalizedLeft.length !== normalizedRight.length) return false;
15928
+ return normalizedLeft.every((mark, index) => inlineTextMarkEqual(mark, normalizedRight[index]));
15929
+ }
15930
+ function inlineTextMarkEqual(left, right) {
15931
+ if (!right || left.kind !== right.kind) return false;
15932
+ switch (left.kind) {
15933
+ case "strong":
15934
+ case "emphasis": return true;
15935
+ case "accent": return right.kind === "accent" && left.tone === right.tone;
15936
+ case "highlight": return right.kind === "highlight" && left.tone === right.tone;
15937
+ }
15938
+ }
15939
+ //#endregion
15940
+ //#region ../blocks/src/system/icons/ids.ts
15941
+ const GENERATED_ICON_SET_ID_PATTERN = /^generated-icon-set:[a-z0-9][a-z0-9:_-]{0,107}$/u;
15942
+ const GENERATED_ICON_ASSET_ID_PATTERN = /^generated-icon-asset:[a-z0-9][a-z0-9:_-]{0,105}$/u;
15943
+ const SITE_ICON_STYLE_GUIDE_VERSION_ID_PATTERN = /^site-icon-style-guide:[a-f0-9]{16}$/u;
15944
+ function normalizeString(value) {
15945
+ if (typeof value !== "string") return null;
15946
+ const normalized = value.trim();
15947
+ return normalized.length > 0 ? normalized : null;
15948
+ }
15949
+ function unsafeAsGeneratedIconSetId(value) {
15950
+ return value;
15951
+ }
15952
+ function unsafeAsGeneratedIconAssetId(value) {
15953
+ return value;
15954
+ }
15955
+ function unsafeAsSiteIconStyleGuideVersionId(value) {
15956
+ return value;
15957
+ }
15958
+ function parseGeneratedIconSetId(value) {
15959
+ const normalized = normalizeString(value);
15960
+ return normalized !== null && GENERATED_ICON_SET_ID_PATTERN.test(normalized) ? unsafeAsGeneratedIconSetId(normalized) : null;
15961
+ }
15962
+ function parseGeneratedIconAssetId(value) {
15963
+ const normalized = normalizeString(value);
15964
+ return normalized !== null && GENERATED_ICON_ASSET_ID_PATTERN.test(normalized) ? unsafeAsGeneratedIconAssetId(normalized) : null;
15965
+ }
15966
+ function parseSiteIconStyleGuideVersionId(value) {
15967
+ if (typeof value !== "string") return null;
15968
+ const normalized = value.trim();
15969
+ return SITE_ICON_STYLE_GUIDE_VERSION_ID_PATTERN.test(normalized) ? unsafeAsSiteIconStyleGuideVersionId(normalized) : null;
15970
+ }
15971
+ //#endregion
15972
+ //#region ../blocks/src/system/icons/semantic.ts
15973
+ const SEMANTIC_ICON_IDS = [
15974
+ "protection-trust",
15975
+ "confidentiality",
15976
+ "care-support",
15977
+ "partnership",
15978
+ "warmth",
15979
+ "community",
15980
+ "personal-guidance",
15981
+ "conversation",
15982
+ "friendly-welcome",
15983
+ "insight-learning",
15984
+ "natural-wellbeing",
15985
+ "growth",
15986
+ "gentle-care",
15987
+ "special-touch",
15988
+ "ideas",
15989
+ "direction",
15990
+ "focus",
15991
+ "quality",
15992
+ "highlight",
15993
+ "verified-quality",
15994
+ "learning",
15995
+ "training",
15996
+ "programmes-dates",
15997
+ "place",
15998
+ "products",
15999
+ "packages",
16000
+ "time",
16001
+ "practical-service",
16002
+ "at-home",
16003
+ "worldwide",
16004
+ "professional-service"
16005
+ ];
16006
+ const featurePreset = ["feature"];
16007
+ [
16008
+ {
16009
+ id: "protection-trust",
16010
+ label: "Protection and trust",
16011
+ tags: [
16012
+ "safe",
16013
+ "safety",
16014
+ "security",
16015
+ "trust",
16016
+ "privacy",
16017
+ "confidence"
16018
+ ],
16019
+ domains: [
16020
+ "wellbeing",
16021
+ "services",
16022
+ "technology"
16023
+ ],
16024
+ tone: ["trustworthy", "practical"],
16025
+ presets: featurePreset
16026
+ },
16027
+ {
16028
+ id: "confidentiality",
16029
+ label: "Confidentiality",
16030
+ tags: [
16031
+ "confidential",
16032
+ "privacy",
16033
+ "secure",
16034
+ "protected",
16035
+ "private"
16036
+ ],
16037
+ domains: [
16038
+ "wellbeing",
16039
+ "coaching",
16040
+ "services",
16041
+ "technology"
16042
+ ],
16043
+ tone: ["trustworthy", "calm"],
16044
+ presets: featurePreset
16045
+ },
16046
+ {
16047
+ id: "care-support",
16048
+ label: "Care and support",
16049
+ tags: [
16050
+ "care",
16051
+ "support",
16052
+ "therapy",
16053
+ "kindness",
16054
+ "help",
16055
+ "compassion"
16056
+ ],
16057
+ domains: [
16058
+ "wellbeing",
16059
+ "coaching",
16060
+ "community"
16061
+ ],
16062
+ tone: ["warm", "human"],
16063
+ presets: featurePreset
16064
+ },
16065
+ {
16066
+ id: "partnership",
16067
+ label: "Partnership",
16068
+ tags: [
16069
+ "partnership",
16070
+ "together",
16071
+ "support",
16072
+ "relationship",
16073
+ "collaboration"
16074
+ ],
16075
+ domains: [
16076
+ "coaching",
16077
+ "community",
16078
+ "services"
16079
+ ],
16080
+ tone: [
16081
+ "warm",
16082
+ "human",
16083
+ "trustworthy"
16084
+ ],
16085
+ presets: featurePreset
16086
+ },
16087
+ {
16088
+ id: "warmth",
16089
+ label: "Warmth",
16090
+ tags: [
16091
+ "heart",
16092
+ "warmth",
16093
+ "care",
16094
+ "love",
16095
+ "wellbeing",
16096
+ "personal"
16097
+ ],
16098
+ domains: [
16099
+ "wellbeing",
16100
+ "community",
16101
+ "hospitality"
16102
+ ],
16103
+ tone: ["warm", "human"],
16104
+ presets: featurePreset
16105
+ },
16106
+ {
16107
+ id: "community",
16108
+ label: "Community",
16109
+ tags: [
16110
+ "community",
16111
+ "group",
16112
+ "people",
16113
+ "team",
16114
+ "members",
16115
+ "belonging"
16116
+ ],
16117
+ domains: [
16118
+ "community",
16119
+ "education",
16120
+ "events"
16121
+ ],
16122
+ tone: ["human", "warm"],
16123
+ presets: featurePreset
16124
+ },
16125
+ {
16126
+ id: "personal-guidance",
16127
+ label: "Personal guidance",
16128
+ tags: [
16129
+ "guidance",
16130
+ "personal",
16131
+ "coach",
16132
+ "expert",
16133
+ "one to one",
16134
+ "trusted"
16135
+ ],
16136
+ domains: [
16137
+ "coaching",
16138
+ "services",
16139
+ "education"
16140
+ ],
16141
+ tone: ["trustworthy", "human"],
16142
+ presets: featurePreset
16143
+ },
16144
+ {
16145
+ id: "conversation",
16146
+ label: "Conversation",
16147
+ tags: [
16148
+ "conversation",
16149
+ "talk",
16150
+ "chat",
16151
+ "communication",
16152
+ "support"
16153
+ ],
16154
+ domains: [
16155
+ "coaching",
16156
+ "community",
16157
+ "services"
16158
+ ],
16159
+ tone: ["human", "warm"],
16160
+ presets: featurePreset
16161
+ },
16162
+ {
16163
+ id: "friendly-welcome",
16164
+ label: "Friendly welcome",
16165
+ tags: [
16166
+ "friendly",
16167
+ "welcome",
16168
+ "positive",
16169
+ "happy",
16170
+ "approachable"
16171
+ ],
16172
+ domains: [
16173
+ "hospitality",
16174
+ "community",
16175
+ "services"
16176
+ ],
16177
+ tone: [
16178
+ "warm",
16179
+ "playful",
16180
+ "human"
16181
+ ],
16182
+ presets: featurePreset
16183
+ },
16184
+ {
16185
+ id: "insight-learning",
16186
+ label: "Insight and learning",
16187
+ tags: [
16188
+ "mind",
16189
+ "insight",
16190
+ "learning",
16191
+ "clarity",
16192
+ "thinking",
16193
+ "therapy"
16194
+ ],
16195
+ domains: [
16196
+ "wellbeing",
16197
+ "coaching",
16198
+ "education"
16199
+ ],
16200
+ tone: ["calm", "practical"],
16201
+ presets: featurePreset
16202
+ },
16203
+ {
16204
+ id: "natural-wellbeing",
16205
+ label: "Natural wellbeing",
16206
+ tags: [
16207
+ "natural",
16208
+ "wellbeing",
16209
+ "growth",
16210
+ "eco",
16211
+ "calm",
16212
+ "organic"
16213
+ ],
16214
+ domains: [
16215
+ "wellbeing",
16216
+ "hospitality",
16217
+ "creative"
16218
+ ],
16219
+ tone: ["calm", "warm"],
16220
+ presets: featurePreset
16221
+ },
16222
+ {
16223
+ id: "growth",
16224
+ label: "Growth",
16225
+ tags: [
16226
+ "growth",
16227
+ "new",
16228
+ "progress",
16229
+ "development",
16230
+ "nurture"
16231
+ ],
16232
+ domains: [
16233
+ "wellbeing",
16234
+ "coaching",
16235
+ "education",
16236
+ "community"
16237
+ ],
16238
+ tone: ["warm", "calm"],
16239
+ presets: featurePreset
16240
+ },
16241
+ {
16242
+ id: "gentle-care",
16243
+ label: "Gentle care",
16244
+ tags: [
16245
+ "gentle",
16246
+ "soft",
16247
+ "care",
16248
+ "wellbeing",
16249
+ "beauty",
16250
+ "calm"
16251
+ ],
16252
+ domains: [
16253
+ "wellbeing",
16254
+ "creative",
16255
+ "hospitality"
16256
+ ],
16257
+ tone: [
16258
+ "warm",
16259
+ "calm",
16260
+ "premium"
16261
+ ],
16262
+ presets: featurePreset
16263
+ },
16264
+ {
16265
+ id: "special-touch",
16266
+ label: "Special touch",
16267
+ tags: [
16268
+ "special",
16269
+ "sparkle",
16270
+ "magic",
16271
+ "polish",
16272
+ "delight",
16273
+ "creative"
16274
+ ],
16275
+ domains: [
16276
+ "creative",
16277
+ "commerce",
16278
+ "hospitality"
16279
+ ],
16280
+ tone: ["playful", "premium"],
16281
+ presets: featurePreset
16282
+ },
16283
+ {
16284
+ id: "ideas",
16285
+ label: "Ideas",
16286
+ tags: [
16287
+ "idea",
16288
+ "innovation",
16289
+ "clarity",
16290
+ "creative",
16291
+ "strategy"
16292
+ ],
16293
+ domains: [
16294
+ "coaching",
16295
+ "education",
16296
+ "creative",
16297
+ "technology"
16298
+ ],
16299
+ tone: ["practical", "playful"],
16300
+ presets: featurePreset
16301
+ },
16302
+ {
16303
+ id: "direction",
16304
+ label: "Direction",
16305
+ tags: [
16306
+ "direction",
16307
+ "navigation",
16308
+ "path",
16309
+ "journey",
16310
+ "guidance"
16311
+ ],
16312
+ domains: [
16313
+ "coaching",
16314
+ "education",
16315
+ "services"
16316
+ ],
16317
+ tone: ["trustworthy", "practical"],
16318
+ presets: featurePreset
16319
+ },
16320
+ {
16321
+ id: "focus",
16322
+ label: "Focus",
16323
+ tags: [
16324
+ "focus",
16325
+ "goal",
16326
+ "outcome",
16327
+ "aim",
16328
+ "strategy"
16329
+ ],
16330
+ domains: [
16331
+ "coaching",
16332
+ "services",
16333
+ "technology"
16334
+ ],
16335
+ tone: ["practical"],
16336
+ presets: featurePreset
16337
+ },
16338
+ {
16339
+ id: "quality",
16340
+ label: "Quality",
16341
+ tags: [
16342
+ "quality",
16343
+ "award",
16344
+ "standard",
16345
+ "achievement",
16346
+ "premium"
16347
+ ],
16348
+ domains: [
16349
+ "services",
16350
+ "education",
16351
+ "commerce"
16352
+ ],
16353
+ tone: ["premium", "trustworthy"],
16354
+ presets: featurePreset
16355
+ },
16356
+ {
16357
+ id: "highlight",
16358
+ label: "Highlight",
16359
+ tags: [
16360
+ "highlight",
16361
+ "featured",
16362
+ "favorite",
16363
+ "quality",
16364
+ "special"
16365
+ ],
16366
+ domains: [
16367
+ "creative",
16368
+ "commerce",
16369
+ "services"
16370
+ ],
16371
+ tone: ["playful", "premium"],
16372
+ presets: featurePreset
16373
+ },
16374
+ {
16375
+ id: "verified-quality",
16376
+ label: "Verified quality",
16377
+ tags: [
16378
+ "verified",
16379
+ "quality",
16380
+ "approved",
16381
+ "trusted",
16382
+ "check"
16383
+ ],
16384
+ domains: [
16385
+ "services",
16386
+ "commerce",
16387
+ "education"
16388
+ ],
16389
+ tone: ["trustworthy", "practical"],
16390
+ presets: featurePreset
16391
+ },
16392
+ {
16393
+ id: "learning",
16394
+ label: "Learning",
16395
+ tags: [
16396
+ "learning",
16397
+ "book",
16398
+ "course",
16399
+ "knowledge",
16400
+ "education"
16401
+ ],
16402
+ domains: [
16403
+ "education",
16404
+ "coaching",
16405
+ "community"
16406
+ ],
16407
+ tone: ["calm", "practical"],
16408
+ presets: featurePreset
16409
+ },
16410
+ {
16411
+ id: "training",
16412
+ label: "Training",
16413
+ tags: [
16414
+ "training",
16415
+ "course",
16416
+ "qualification",
16417
+ "school",
16418
+ "education"
16419
+ ],
16420
+ domains: ["education", "coaching"],
16421
+ tone: ["trustworthy", "practical"],
16422
+ presets: featurePreset
16423
+ },
16424
+ {
16425
+ id: "programmes-dates",
16426
+ label: "Programmes and dates",
16427
+ tags: [
16428
+ "calendar",
16429
+ "dates",
16430
+ "schedule",
16431
+ "events",
16432
+ "programme",
16433
+ "classes"
16434
+ ],
16435
+ domains: [
16436
+ "events",
16437
+ "education",
16438
+ "community"
16439
+ ],
16440
+ tone: ["practical"],
16441
+ presets: featurePreset
16442
+ },
16443
+ {
16444
+ id: "place",
16445
+ label: "Place",
16446
+ tags: [
16447
+ "place",
16448
+ "location",
16449
+ "local",
16450
+ "venue",
16451
+ "destination"
16452
+ ],
16453
+ domains: [
16454
+ "events",
16455
+ "hospitality",
16456
+ "services"
16457
+ ],
16458
+ tone: ["practical"],
16459
+ presets: featurePreset
16460
+ },
16461
+ {
16462
+ id: "products",
16463
+ label: "Products",
16464
+ tags: [
16465
+ "products",
16466
+ "shop",
16467
+ "store",
16468
+ "retail",
16469
+ "commerce"
16470
+ ],
16471
+ domains: ["commerce"],
16472
+ tone: ["practical", "premium"],
16473
+ presets: featurePreset
16474
+ },
16475
+ {
16476
+ id: "packages",
16477
+ label: "Packages",
16478
+ tags: [
16479
+ "package",
16480
+ "bundle",
16481
+ "delivery",
16482
+ "offer",
16483
+ "product"
16484
+ ],
16485
+ domains: ["commerce", "services"],
16486
+ tone: ["practical"],
16487
+ presets: featurePreset
16488
+ },
16489
+ {
16490
+ id: "time",
16491
+ label: "Time",
16492
+ tags: [
16493
+ "time",
16494
+ "duration",
16495
+ "schedule",
16496
+ "availability",
16497
+ "appointment"
16498
+ ],
16499
+ domains: [
16500
+ "events",
16501
+ "services",
16502
+ "wellbeing"
16503
+ ],
16504
+ tone: ["practical", "calm"],
16505
+ presets: featurePreset
16506
+ },
16507
+ {
16508
+ id: "practical-service",
16509
+ label: "Practical service",
16510
+ tags: [
16511
+ "service",
16512
+ "repair",
16513
+ "setup",
16514
+ "practical",
16515
+ "tools",
16516
+ "support"
16517
+ ],
16518
+ domains: ["services", "technology"],
16519
+ tone: ["practical"],
16520
+ presets: featurePreset
16521
+ },
16522
+ {
16523
+ id: "at-home",
16524
+ label: "At home",
16525
+ tags: [
16526
+ "home",
16527
+ "domestic",
16528
+ "comfort",
16529
+ "local",
16530
+ "space"
16531
+ ],
16532
+ domains: [
16533
+ "hospitality",
16534
+ "services",
16535
+ "community"
16536
+ ],
16537
+ tone: ["warm", "calm"],
16538
+ presets: featurePreset
16539
+ },
16540
+ {
16541
+ id: "worldwide",
16542
+ label: "Worldwide",
16543
+ tags: [
16544
+ "global",
16545
+ "online",
16546
+ "worldwide",
16547
+ "remote",
16548
+ "international"
16549
+ ],
16550
+ domains: [
16551
+ "technology",
16552
+ "education",
16553
+ "services"
16554
+ ],
16555
+ tone: ["practical"],
16556
+ presets: featurePreset
16557
+ },
16558
+ {
16559
+ id: "professional-service",
16560
+ label: "Professional service",
16561
+ tags: [
16562
+ "business",
16563
+ "professional",
16564
+ "consulting",
16565
+ "service",
16566
+ "work"
16567
+ ],
16568
+ domains: [
16569
+ "services",
16570
+ "technology",
16571
+ "coaching"
16572
+ ],
16573
+ tone: ["practical", "trustworthy"],
16574
+ presets: featurePreset
16575
+ }
16576
+ ].map(({ id, label }) => ({
16577
+ id,
16578
+ label
16579
+ }));
16580
+ function isSemanticIconId(value) {
16581
+ return typeof value === "string" && SEMANTIC_ICON_IDS.includes(value);
16582
+ }
16583
+ //#endregion
16584
+ //#region ../blocks/src/system/icons/value.ts
16585
+ const NO_BLOCK_ICON_VALUE = { kind: "none" };
16586
+ function isRecord$11(value) {
16587
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16588
+ }
16589
+ function parseBlockIconValue(value) {
16590
+ if (isSemanticIconId(value)) return {
16591
+ kind: "semantic",
16592
+ iconId: value
16593
+ };
16594
+ if (!isRecord$11(value)) return NO_BLOCK_ICON_VALUE;
16595
+ switch (value.kind) {
16596
+ case "none": return NO_BLOCK_ICON_VALUE;
16597
+ case "semantic": return isSemanticIconId(value.iconId) ? {
16598
+ kind: "semantic",
16599
+ iconId: value.iconId
16600
+ } : NO_BLOCK_ICON_VALUE;
16601
+ case "generated": {
16602
+ const setId = parseGeneratedIconSetId(value.setId);
16603
+ const assetId = parseGeneratedIconAssetId(value.assetId);
16604
+ const styleGuideVersionId = parseSiteIconStyleGuideVersionId(value.styleGuideVersionId);
16605
+ if (setId !== null && assetId !== null && styleGuideVersionId !== null) return {
16606
+ kind: "generated",
16607
+ setId,
16608
+ assetId,
16609
+ styleGuideVersionId
16610
+ };
16611
+ return NO_BLOCK_ICON_VALUE;
16612
+ }
16613
+ default: return NO_BLOCK_ICON_VALUE;
16614
+ }
16615
+ }
16616
+ //#endregion
16617
+ //#region ../blocks/src/lib/typeGuards.ts
16618
+ /**
16619
+ * Strict record guard: excludes null, primitives, and arrays.
16620
+ */
16621
+ function isRecord$10(value) {
16622
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16623
+ }
16624
+ /**
16625
+ * Non-null object guard: includes arrays for behavior parity with legacy helpers.
16626
+ *
16627
+ * NOTE: This intentionally narrows to UnknownRecord for compatibility with existing
16628
+ * callsites even though arrays pass at runtime. Follow-up cleanup should replace
16629
+ * this API with a more precise predicate type and migrate consumers.
16630
+ */
16631
+ function isObjectRecord$1(value) {
16632
+ return typeof value === "object" && value !== null;
16633
+ }
16634
+ //#endregion
16635
+ //#region ../blocks/src/system/manifest/richTextSchema.ts
16636
+ /**
16637
+ * Shared Rich Text Schema
16638
+ *
16639
+ * This module provides a single source of truth for TipTap rich text validation
16640
+ * across both the block-form editor and the block rendering system.
16641
+ *
16642
+ * Rich-text fields use one canonical root shape:
16643
+ * `{ type: 'doc', content: [...] }`.
16644
+ */
16645
+ /**
16646
+ * TipTap document node schema (recursive structure)
16647
+ *
16648
+ * Supports the full TipTap document structure including:
16649
+ * - type: Node type (e.g., 'doc', 'paragraph', 'text')
16650
+ * - content: Nested array of child nodes
16651
+ * - text: Text content for text nodes
16652
+ * - attrs: Node attributes (e.g., alignment, color)
16653
+ * - marks: Text formatting marks (e.g., bold, italic)
16654
+ */
16655
+ const TipTapMarkSchema = z.object({
16656
+ type: z.string().min(1),
16657
+ attrs: z.record(z.string(), z.unknown()).optional()
16658
+ }).strict();
16659
+ const TipTapNodeSchema = z.lazy(() => z.object({
16660
+ type: z.string().min(1),
16661
+ content: z.array(TipTapNodeSchema).optional(),
16662
+ text: z.string().optional(),
16663
+ attrs: z.record(z.string(), z.unknown()).optional(),
16664
+ marks: z.array(TipTapMarkSchema).optional()
16665
+ }).strict().superRefine((node, ctx) => {
16666
+ if (node.type === "doc") ctx.addIssue({
16667
+ code: "custom",
16668
+ path: ["type"],
16669
+ message: "Document nodes are only valid at the rich-text root"
16670
+ });
16671
+ if (node.type === "text") {
16672
+ if (node.text === void 0 || node.text.length === 0) ctx.addIssue({
16673
+ code: "custom",
16674
+ path: ["text"],
16675
+ message: "Text nodes require a non-empty text value"
16676
+ });
16677
+ if (node.content !== void 0) ctx.addIssue({
16678
+ code: "custom",
16679
+ path: ["content"],
16680
+ message: "Text nodes cannot contain child nodes"
16681
+ });
16682
+ return;
16683
+ }
16684
+ if (node.text !== void 0) ctx.addIssue({
16685
+ code: "custom",
16686
+ path: ["text"],
16687
+ message: "Only text nodes can contain a text value"
16688
+ });
16689
+ }));
16690
+ /**
16691
+ * Canonical schema for a TipTap rich-text document root.
16692
+ *
16693
+ * Stricter than {@link TipTapNodeSchema}: `type` is literally `'doc'` and
16694
+ * `content` is required (possibly empty). This is the shape every producer
16695
+ * (dummy content, editor output, migrations) must emit, and the shape every
16696
+ * non-legacy consumer can rely on.
16697
+ *
16698
+ */
16699
+ const RichTextDocSchema = z.object({
16700
+ type: z.literal("doc"),
16701
+ content: z.array(TipTapNodeSchema)
16702
+ }).strict();
16703
+ /**
16704
+ * Type guard for {@link RichTextDoc}.
16705
+ *
16706
+ * Returns `true` only for the canonical unwrapped form. The shared structural
16707
+ * contract lives in `@riverbankcms/core`; parity tests keep its dependency-free
16708
+ * guard aligned with this detailed Zod validator.
16709
+ */
16710
+ function isRichTextDoc(value) {
16711
+ return RichTextDocSchema.safeParse(value).success;
16712
+ }
16713
+ //#endregion
16714
+ //#region ../blocks/src/lib/media.ts
16715
+ const PUBLIC_DOWNLOAD_MEDIA_TYPES = [
16716
+ "document",
16717
+ "spreadsheet",
16718
+ "archive",
16719
+ "audio"
16720
+ ];
16721
+ //#endregion
16722
+ //#region ../blocks/src/system/manifest/fieldValidation/writeCanonicalization.ts
16723
+ function allGroupChildPlans(plan) {
16724
+ return plan.childSelection.kind === "all" ? plan.childSelection.fields : plan.childSelection.tabs.flatMap((tab) => tab.fields);
16725
+ }
16726
+ function selectedGroupChildPlans(plan, value) {
16727
+ if (plan.childSelection.kind === "all") return plan.childSelection.fields;
16728
+ if (!isPlainObject$3(value)) return [];
16729
+ const selectedType = value[plan.childSelection.activeTabField];
16730
+ return plan.childSelection.tabs.find((tab) => tab.typeId === selectedType)?.fields ?? [];
16731
+ }
16732
+ function canonicalizeObjectForWrite(plans, value) {
16733
+ if (!isPlainObject$3(value)) return value;
16734
+ const canonical = { ...value };
16735
+ for (const plan of plans) {
16736
+ if (!(plan.fieldId in canonical)) continue;
16737
+ canonical[plan.fieldId] = canonicalizeFieldValue(plan, canonical[plan.fieldId]);
16738
+ }
16739
+ return omitUndefined(canonical);
16740
+ }
16741
+ function canonicalizeFieldValue(plan, value) {
16742
+ switch (plan.kind) {
16743
+ case "media": return canonicalizeMedia(value);
16744
+ case "group": return plan.childSelection.kind === "discriminatedTabs" ? canonicalizeDiscriminatedGroup(plan, value) : canonicalizeObjectForWrite(plan.childSelection.fields, value);
16745
+ case "repeater": return canonicalizeRepeater(plan, value);
16746
+ case "string":
16747
+ case "number":
16748
+ case "boolean":
16749
+ case "richText":
16750
+ case "inlineText":
16751
+ case "link":
16752
+ case "select":
16753
+ case "passthrough": return value;
16754
+ }
16755
+ }
16756
+ function canonicalizeMedia(value) {
16757
+ if (!isPlainObject$3(value)) return value;
16758
+ const canonical = { ...value };
16759
+ if (mediaIdentifierSchema.safeParse(canonical.identifier).success) deleteEnvSpecificKeys(canonical);
16760
+ return canonical;
16761
+ }
16762
+ function canonicalizeRepeater(plan, value) {
16763
+ if (!isUnknownArray$1(value)) return value;
16764
+ return value.map((item) => {
16765
+ if (!isPlainObject$3(item)) return item;
16766
+ const childPlans = plan.repeatedItemVariants ? plan.repeatedItemVariants.find((variant) => variant.typeId === item._type)?.fields : plan.repeatedItemPlan;
16767
+ return childPlans ? canonicalizeObjectForWrite(childPlans, item) : item;
16768
+ });
16769
+ }
16770
+ function canonicalizeDiscriminatedGroup(plan, value) {
16771
+ if (!isPlainObject$3(value) || plan.childSelection.kind !== "discriminatedTabs") return value;
16772
+ const selection = plan.childSelection;
16773
+ const selectedPlans = selectedGroupChildPlans(plan, value);
16774
+ const selectedIds = rootFieldIds(selectedPlans);
16775
+ const allChildIds = rootFieldIds(allGroupChildPlans(plan));
16776
+ const canonical = omitUndefined(canonicalizeObjectForWrite(selectedPlans, Object.fromEntries(Object.entries(value).filter(([key]) => key === selection.activeTabField || selectedIds.has(key) || !allChildIds.has(key)))));
16777
+ if (plan.valueContract !== "boxBackground") return canonical;
16778
+ const parsed = boxBackgroundSchema.safeParse(canonical);
16779
+ return parsed.success ? parsed.data : canonical;
16780
+ }
16781
+ function rootFieldIds(plans) {
16782
+ return new Set(plans.map((child) => child.fieldId.split(".")[0] ?? child.fieldId));
16783
+ }
16784
+ function omitUndefined(value) {
16785
+ if (isUnknownArray$1(value)) return value.map(omitUndefined);
16786
+ if (!isPlainObject$3(value)) return value;
16787
+ return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== void 0).map(([key, field]) => [key, omitUndefined(field)]));
16788
+ }
16789
+ function isPlainObject$3(value) {
16790
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16791
+ }
16792
+ function isUnknownArray$1(value) {
16793
+ return Array.isArray(value);
16794
+ }
16795
+ //#endregion
16796
+ //#region ../blocks/src/system/manifest/fieldValidation/index.ts
16797
+ const TEL_RE = /^[+()0-9\s-]{3,}$/;
16798
+ const SLUG_PATTERN = /^(?:[a-z0-9]+(?:-[a-z0-9]+)*)(?:\/(?:[a-z0-9]+(?:-[a-z0-9]+)*))*$/;
16799
+ const ALLOWED_URL_PROTOCOLS = ["http:", "https:"];
16800
+ const DATE_PATTERN = /^(?<year>[1-9]\d{3})-(?<month>\d{2})-(?<day>\d{2})$/;
16801
+ const TIME_PATTERN = /^(?<hours>[01]\d|2[0-3]):(?<minutes>[0-5]\d)$/;
16802
+ const DATETIME_PATTERN = /^(?<date>[1-9]\d{3}-\d{2}-\d{2})T(?<time>(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d)(?:\.\d+)?(?<offset>Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
16803
+ function assertNever$1(value) {
16804
+ throw new Error(`Unhandled field validation variant: ${JSON.stringify(value)}`);
16805
+ }
16806
+ function validationContext(options = {}) {
16807
+ const isDraft = options.mode === "draft";
16808
+ return {
16809
+ allowNull: options.allowNull ?? true,
16810
+ allowIncomplete: isDraft || (options.allowIncomplete ?? false)
16811
+ };
16812
+ }
16813
+ function strictValidationContext(options = {}) {
16814
+ return {
16815
+ ...validationContext(options),
16816
+ allowNull: options.allowNull ?? false
16817
+ };
16818
+ }
16819
+ function formatFieldPath(path) {
16820
+ return path.map((segment) => String(segment)).join(".");
16821
+ }
16822
+ function toZodIssuePath(path) {
16823
+ return [...path];
16824
+ }
16825
+ function parseFieldPathKey(key) {
16826
+ if (!key) return [];
16827
+ return key.split(".").map((segment) => {
16828
+ const numeric = Number(segment);
16829
+ return Number.isInteger(numeric) && String(numeric) === segment ? numeric : segment;
16830
+ });
16831
+ }
16832
+ function fieldIssueToMessage(issue) {
16833
+ switch (issue.kind) {
16834
+ case "required": return "This field is required";
16835
+ case "wrongType": return `${issue.label} needs ${issue.expected}`;
16836
+ case "tooLong": return `Use ${issue.maximum} characters or fewer`;
16837
+ case "invalidEmail": return "Enter a valid email address";
16838
+ case "invalidPhone": return "Enter a valid phone number";
16839
+ case "invalidPattern": return "Check the format and try again";
16840
+ case "invalidUrl": return "Enter a valid web address";
16841
+ case "invalidDate": return "Choose a valid date";
16842
+ case "invalidTime": return "Choose a valid time";
16843
+ case "invalidDateTime": return "Choose a valid date and time";
16844
+ case "invalidNumber": return `${issue.label} needs a number`;
16845
+ case "tooSmall": return `${issue.label} must be ${issue.minimum} or more`;
16846
+ case "tooLarge": return `${issue.label} must be ${issue.maximum} or less`;
16847
+ case "invalidSlug": return `${issue.label} can only use lowercase letters, numbers, and dashes`;
16848
+ case "invalidOption": return `Choose one of the available options for ${issue.label}`;
16849
+ case "tooFewItems": return `${issue.label} must have at least ${issue.minimum} ${pluralize(issue.itemLabel, issue.minimum)}`;
16850
+ case "tooManyItems": return `${issue.label} must have at most ${issue.maximum} ${pluralize(issue.itemLabel, issue.maximum)}`;
16851
+ case "invalidRichText": return `${issue.label} must be valid rich text`;
16852
+ case "invalidInlineText": return `${issue.label} must be valid styled text`;
16853
+ case "invalidMedia": return `${issue.label} must be a media object with an identifier`;
16854
+ case "invalidBoxBackground": return `${issue.label} must use a valid background type and matching fields`;
16855
+ case "invalidLink": return `${issue.label} must be a valid link`;
16856
+ case "invalidReference": return `${issue.label} must reference a valid item`;
16857
+ default: return assertNever$1(issue);
16858
+ }
16859
+ }
16860
+ function pluralize(label, count) {
16861
+ if (count === 1) return label;
16862
+ return label.endsWith("s") ? label : `${label}s`;
16863
+ }
16864
+ function valueContractForField(field) {
16865
+ return field.ui?.valueContract === "boxBackground" ? { valueContract: "boxBackground" } : {};
16866
+ }
16867
+ function deriveTabGroupValidationPlan(field, path, required) {
16868
+ return {
16869
+ ...makePlan("group", "object", field, path, required, requiredConstraints(required)),
16870
+ ...valueContractForField(field),
16871
+ childSelection: field.activeTabField ? {
16872
+ kind: "discriminatedTabs",
16873
+ activeTabField: field.activeTabField,
16874
+ tabs: field.tabs.map((tab) => ({
16875
+ typeId: tab.id,
16876
+ fields: deriveNestedFieldPlans(tab.fields, path)
16877
+ }))
16878
+ } : {
16879
+ kind: "all",
16880
+ fields: field.tabs.flatMap((tab) => deriveNestedFieldPlans(tab.fields, path))
16881
+ }
16882
+ };
16883
+ }
16884
+ function deriveFieldValidationPlan(field, path = [field.id]) {
16885
+ const required = Boolean(field.required);
16886
+ switch (field.type) {
16887
+ case "text": {
16888
+ const inputType = field.ui?.inputType;
16889
+ if (inputType === "number") {
16890
+ const constraints = commonConstraints(required, { kind: "numberType" });
16891
+ if (typeof field.ui?.min === "number") constraints.push({
16892
+ kind: "numberMin",
16893
+ minimum: field.ui.min
16894
+ });
16895
+ if (typeof field.ui?.max === "number") constraints.push({
16896
+ kind: "numberMax",
16897
+ maximum: field.ui.max
16898
+ });
16899
+ return makePlan("number", "number", field, path, required, constraints);
16900
+ }
16901
+ const constraints = commonConstraints(required, { kind: field.ui?.widget === "iconPicker" ? "blockIconValue" : "textType" });
16902
+ if (inputType === "email") constraints.push({ kind: "emailFormat" });
16903
+ if (inputType === "tel") constraints.push({ kind: "phoneFormat" });
16904
+ if (field.ui?.pattern) constraints.push({
16905
+ kind: "patternFormat",
16906
+ pattern: field.ui.pattern
16907
+ });
16908
+ if (typeof field.maxLength === "number") constraints.push({
16909
+ kind: "maxLength",
16910
+ maximum: field.maxLength
16911
+ });
16912
+ return makePlan("string", "string", field, path, required, constraints);
16913
+ }
16914
+ case "number": {
16915
+ const constraints = commonConstraints(required, { kind: "numberType" });
16916
+ if (typeof field.min === "number") constraints.push({
16917
+ kind: "numberMin",
16918
+ minimum: field.min
16919
+ });
16920
+ if (typeof field.max === "number") constraints.push({
16921
+ kind: "numberMax",
16922
+ maximum: field.max
16923
+ });
16924
+ return makePlan("number", "number", field, path, required, constraints);
16925
+ }
16926
+ case "slug": {
16927
+ const constraints = commonConstraints(required, { kind: "textType" }, { kind: "slugFormat" });
16928
+ if (typeof field.maxLength === "number") constraints.push({
16929
+ kind: "maxLength",
16930
+ maximum: field.maxLength
16931
+ });
16932
+ return makePlan("string", "string", field, path, required, constraints);
16933
+ }
16934
+ case "url": return makePlan("string", "string", field, path, required, commonConstraints(required, { kind: "textType" }, {
16935
+ kind: "urlFormat",
16936
+ allowRelative: Boolean(field.allowRelative)
16937
+ }));
16938
+ case "reference": return makePlan("string", "string", field, path, required, commonConstraints(required, { kind: "referenceText" }));
16939
+ case "date": return makePlan("string", "string", field, path, required, commonConstraints(required, { kind: "textType" }, { kind: "dateFormat" }));
16940
+ case "time": return makePlan("string", "string", field, path, required, commonConstraints(required, { kind: "textType" }, { kind: "timeFormat" }));
16941
+ case "datetime": return makePlan("string", "string", field, path, required, commonConstraints(required, { kind: "textType" }, { kind: "datetimeFormat" }));
16942
+ case "select": return makePlan("select", "select", field, path, required, commonConstraints(required, {
16943
+ kind: "selectOption",
16944
+ source: resolveSelectConstraintSource(field),
16945
+ multiple: Boolean(field.multiple)
16946
+ }));
16947
+ case "richText": return makePlan("richText", "object", field, path, required, commonConstraints(required, { kind: "richTextDoc" }));
16948
+ case "inlineText": {
16949
+ const constraints = commonConstraints(required, { kind: "inlineText" });
16950
+ if (typeof field.maxLength === "number") constraints.push({
16951
+ kind: "maxPlainTextLength",
16952
+ maximum: field.maxLength
16953
+ });
16954
+ return {
16955
+ ...makePlan("inlineText", "array", field, path, required, constraints),
16956
+ ...field.plainTextField ? { plainTextFallbackPath: parseFieldPathKey(field.plainTextField) } : {}
16957
+ };
16958
+ }
16959
+ case "media":
16960
+ case "file": return {
16961
+ ...makePlan("media", "object", field, path, required, commonConstraints(required, { kind: "mediaObject" })),
16962
+ allowedMediaTypes: field.type === "media" ? field.mediaKinds ?? DEFAULT_MEDIA_FIELD_KINDS : field.fileKinds ?? PUBLIC_FILE_FIELD_KINDS
16963
+ };
16964
+ case "link": return makePlan("link", "object", field, path, required, commonConstraints(required, { kind: "linkObject" }));
16965
+ case "boolean": return makePlan("boolean", "boolean", field, path, required, requiredConstraints(required));
16966
+ case "repeater": return {
16967
+ ...makePlan("repeater", "array", field, path, required, commonConstraints(required, { kind: "arrayType" })),
16968
+ ...field.polymorphic && field.itemTypes ? { repeatedItemVariants: deriveRepeatedItemVariants(field, [...path, 0]) } : { repeatedItemPlan: deriveNestedFieldPlans(getRepeaterFields(field), [...path, 0]) },
16969
+ constraints: withRepeaterItemConstraints(field, commonConstraints(required, { kind: "arrayType" }))
16970
+ };
16971
+ case "group":
16972
+ case "modal": return {
16973
+ ...makePlan("group", "object", field, path, required, requiredConstraints(required)),
16974
+ childSelection: {
16975
+ kind: "all",
16976
+ fields: deriveNestedFieldPlans(field.schema?.fields ?? [], path)
16977
+ },
16978
+ allowNullChildren: field.type === "group" && field.ui?.widget === "locationPicker"
16979
+ };
16980
+ case "tabGroup": return deriveTabGroupValidationPlan(field, path, required);
16981
+ case "presetOrCustom":
16982
+ case "contentTypeSelect":
16983
+ case "entryPicker": return makePlan("passthrough", "unknown", field, path, false, []);
16984
+ default: return assertNever$1(field);
16985
+ }
16986
+ }
16987
+ function deriveFieldValidationPlans(fields, parentPath = []) {
16988
+ return fields.map((field) => deriveFieldValidationPlan(field, [...parentPath, field.id]));
16989
+ }
16990
+ function commonConstraints(required, ...specific) {
16991
+ return required ? [{ kind: "required" }, ...specific] : [...specific];
16992
+ }
16993
+ function requiredConstraints(required) {
16994
+ return required ? [{ kind: "required" }] : [];
16995
+ }
16996
+ function makePlan(kind, valueKind, field, path, required, constraints) {
16997
+ return {
16998
+ kind,
16999
+ valueKind,
17000
+ fieldId: field.id,
17001
+ label: field.label,
17002
+ fieldType: field.type,
17003
+ path,
17004
+ required,
17005
+ visibility: fieldVisibility(field),
17006
+ constraints
17007
+ };
17008
+ }
17009
+ function fieldVisibility(field) {
17010
+ if (field.ui?.hidden) return { kind: "hidden" };
17011
+ if (field.visibleRoles && field.visibleRoles.length > 0) return {
17012
+ kind: "roleRestricted",
17013
+ roles: field.visibleRoles
17014
+ };
17015
+ return { kind: "visible" };
17016
+ }
17017
+ function withRepeaterItemConstraints(field, constraints) {
17018
+ const next = [...constraints];
17019
+ if (typeof field.minItems === "number") next.push({
17020
+ kind: "minItems",
17021
+ minimum: field.minItems
17022
+ });
17023
+ if (typeof field.maxItems === "number") next.push({
17024
+ kind: "maxItems",
17025
+ maximum: field.maxItems
17026
+ });
17027
+ return next;
17028
+ }
17029
+ function deriveRepeatedItemVariants(field, parentPath) {
17030
+ return Object.entries(field.itemTypes ?? {}).map(([typeId, itemType]) => ({
17031
+ typeId,
17032
+ fields: deriveNestedFieldPlans(getFlattenedRepeaterFields(itemType.fields), parentPath)
17033
+ }));
17034
+ }
17035
+ function deriveNestedFieldPlans(fields, parentPath) {
17036
+ return deriveFieldValidationPlans(fields, parentPath);
17037
+ }
17038
+ function getRepeaterFields(field) {
17039
+ return getFlattenedRepeaterFields(field.polymorphic && field.itemTypes ? Object.values(field.itemTypes).flatMap((itemType) => itemType.fields) : field.schema?.fields ?? []);
17040
+ }
17041
+ function getFlattenedRepeaterFields(fields) {
17042
+ return fields.flatMap((child) => {
17043
+ if (child.type === "group" && child.ui?.flattenInRepeater) return child.schema?.fields ?? [];
17044
+ return child;
17045
+ });
17046
+ }
17047
+ function resolveSelectConstraintSource(field) {
17048
+ const source = normalizeSelectSource(field);
17049
+ switch (source.kind) {
17050
+ case "static": return {
17051
+ kind: "static",
17052
+ values: field.options.map((option) => option.value)
17053
+ };
17054
+ case "site":
17055
+ case "sdk": return {
17056
+ kind: "runtime",
17057
+ source: source.kind
17058
+ };
17059
+ default: return assertNever$1(source);
17060
+ }
17061
+ }
17062
+ function normalizeStoredContentValues(plans, content, options = {}) {
17063
+ if (!content || typeof content !== "object" || Array.isArray(content)) return {};
17064
+ const planMap = new Map(plans.map((plan) => [plan.fieldId, plan]));
17065
+ const normalized = {};
17066
+ const ctx = validationContext(options);
17067
+ for (const [key, value] of Object.entries(content)) {
17068
+ const plan = planMap.get(key);
17069
+ normalized[key] = plan ? normalizeStoredFieldValueWithContext(plan, value, ctx) : value;
17070
+ }
17071
+ normalizeInlineTextFallbacks(plans, normalized, ctx);
17072
+ return normalized;
17073
+ }
17074
+ /**
17075
+ * Produces the portable representation accepted by content persistence.
17076
+ * Runtime-hydrated media fields are stripped only when a canonical identifier
17077
+ * is already present; unresolved environment-specific references remain
17078
+ * invalid and must be handled by the site-scoped media write normalizer first.
17079
+ */
17080
+ function normalizeManifestContentForWrite(plans, content, options = {}) {
17081
+ return canonicalizeObjectForWrite(plans, normalizeStoredContentValues(plans, content, options));
17082
+ }
17083
+ function normalizeStoredFieldValueWithContext(plan, value, ctx) {
17084
+ if (value === null && !plan.required) return;
17085
+ switch (plan.kind) {
17086
+ case "group": return normalizeStoredObjectChildren(selectedGroupChildPlans(plan, value), value, childValidationContext(plan, ctx));
17087
+ case "repeater": return normalizeStoredRepeaterItems(plan, value, ctx);
17088
+ case "string":
17089
+ case "number":
17090
+ case "boolean":
17091
+ case "richText":
17092
+ case "inlineText":
17093
+ case "media":
17094
+ case "link":
17095
+ case "select":
17096
+ case "passthrough": return normalizeFieldValue(plan, value, ctx);
17097
+ default: return assertNever$1(plan);
17098
+ }
17099
+ }
17100
+ function normalizeStoredObjectChildren(children, value, ctx) {
17101
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
17102
+ const normalized = { ...value };
17103
+ for (const child of children) normalized[child.fieldId] = normalizeStoredFieldValueWithContext(child, normalized[child.fieldId], ctx);
17104
+ normalizeInlineTextFallbacks(children, normalized, ctx);
17105
+ return normalized;
17106
+ }
17107
+ function normalizeStoredRepeaterItems(plan, value, ctx) {
17108
+ if (!isUnknownArray(value)) return value;
17109
+ return value.map((item) => {
17110
+ if (!item || typeof item !== "object" || Array.isArray(item)) return item;
17111
+ const itemRecord = item;
17112
+ const fields = plan.repeatedItemVariants ? plan.repeatedItemVariants.find((variant) => variant.typeId === itemRecord._type)?.fields : plan.repeatedItemPlan;
17113
+ if (!fields) return item;
17114
+ return normalizeStoredObjectChildren(fields, itemRecord, ctx);
17115
+ });
17116
+ }
17117
+ function normalizeFieldValue(plan, value, ctx) {
17118
+ switch (plan.kind) {
17119
+ case "number": return normalizeNumberInput(value, ctx.allowNull);
17120
+ case "string": return acceptsOptionalBlankAsMissing(plan, value) ? void 0 : value;
17121
+ case "group": return normalizeObjectChildren(selectedGroupChildPlans(plan, value), value, childValidationContext(plan, ctx));
17122
+ case "repeater": return normalizeRepeaterItems(plan, value, ctx);
17123
+ case "boolean":
17124
+ case "richText":
17125
+ case "inlineText":
17126
+ case "link":
17127
+ case "select":
17128
+ case "passthrough": return value;
17129
+ case "media": return value;
17130
+ default: return assertNever$1(plan);
17131
+ }
17132
+ }
17133
+ function normalizeObjectChildren(children, value, ctx) {
17134
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
17135
+ const normalized = { ...value };
17136
+ for (const child of children) normalized[child.fieldId] = normalizeFieldValue(child, normalized[child.fieldId], ctx);
17137
+ normalizeInlineTextFallbacks(children, normalized, ctx);
17138
+ return normalized;
17139
+ }
17140
+ function normalizeInlineTextFallbacks(plans, values, ctx) {
17141
+ for (const plan of plans) {
17142
+ if (plan.kind !== "inlineText" || !plan.plainTextFallbackPath) continue;
17143
+ if (!isMissingForPlan(plan, values[plan.fieldId]) && !(ctx.allowIncomplete && !parseInlineTextFromUnknown(values[plan.fieldId]).ok)) continue;
17144
+ const fallback = getAtFieldPath(values, plan.plainTextFallbackPath);
17145
+ if (typeof fallback !== "string" || fallback.trim().length === 0) continue;
17146
+ values[plan.fieldId] = normalizeFieldValue(plan, plainTextToInlineText(fallback), ctx);
17147
+ }
17148
+ }
17149
+ function getAtFieldPath(source, path) {
17150
+ return path.reduce((current, segment) => {
17151
+ if (!current || typeof current !== "object" || Array.isArray(current)) return void 0;
17152
+ return current[String(segment)];
17153
+ }, source);
17154
+ }
17155
+ function normalizeRepeaterItems(plan, value, ctx) {
17156
+ if (!isUnknownArray(value)) return value;
17157
+ return value.map((item) => {
17158
+ if (!item || typeof item !== "object" || Array.isArray(item)) return item;
17159
+ const itemRecord = item;
17160
+ const fields = plan.repeatedItemVariants ? plan.repeatedItemVariants.find((variant) => variant.typeId === itemRecord._type)?.fields : plan.repeatedItemPlan;
17161
+ if (!fields) return item;
17162
+ return normalizeObjectChildren(fields, itemRecord, ctx);
17163
+ });
17164
+ }
17165
+ function normalizeNumberInput(value, _allowNull) {
17166
+ if (value === "") return void 0;
17167
+ if (typeof value === "string" && value.trim() !== "") return Number(value);
17168
+ return value;
17169
+ }
17170
+ function acceptsOptionalBlankAsMissing(plan, value) {
17171
+ if (plan.required || typeof value !== "string" || value.trim() !== "") return false;
17172
+ return plan.constraints.some((constraint) => constraint.kind === "urlFormat" || constraint.kind === "slugFormat" || constraint.kind === "emailFormat" || constraint.kind === "phoneFormat" || constraint.kind === "patternFormat");
17173
+ }
17174
+ function validateNormalizedFieldValue(plan, value, ctx) {
17175
+ if (value === null && (ctx.allowNull || acceptsOptionalNullValue(plan))) return [];
17176
+ if (isMissingForPlan(plan, value)) return shouldRequireValue(plan, value, ctx) ? [requiredIssue(plan, value)] : [];
17177
+ switch (plan.kind) {
17178
+ case "string": return validateStringPlan(plan, value);
17179
+ case "number": return validateNumberPlan(plan, value);
17180
+ case "boolean": return typeof value === "boolean" ? [] : [issue(plan, "wrongType", { expected: "a true/false value" })];
17181
+ case "select": return validateSelectPlan(plan, value);
17182
+ case "richText": return isValidRichTextDoc(value) ? [] : [issue(plan, "invalidRichText")];
17183
+ case "inlineText": return validateInlineTextPlan(plan, value);
17184
+ case "media": return isPlainObject$2(value) && mediaIdentifierSchema.safeParse(value.identifier).success && (value.type === void 0 || typeof value.type === "string" && plan.allowedMediaTypes.includes(value.type)) ? [] : [issue(plan, "invalidMedia")];
17185
+ case "link": return linkSchema.safeParse(value).success ? [] : [issue(plan, "invalidLink")];
17186
+ case "repeater": return validateRepeaterPlan(plan, value, ctx);
17187
+ case "group": return validateGroupPlan(plan, value, ctx);
17188
+ case "passthrough": return [];
17189
+ default: return assertNever$1(plan);
17190
+ }
17191
+ }
17192
+ function shouldRequireValue(plan, value, ctx) {
17193
+ if (!plan.required || ctx.allowIncomplete) return false;
17194
+ if (value === null && ctx.allowNull) return false;
17195
+ return true;
17196
+ }
17197
+ function acceptsOptionalNullValue(plan) {
17198
+ return !plan.required && (plan.kind === "media" || plan.kind === "number");
17199
+ }
17200
+ function isMissingForPlan(plan, value) {
17201
+ if (value === void 0) return true;
17202
+ if (value === null) return plan.required;
17203
+ if (typeof value === "string" && value.trim() === "") return true;
17204
+ if (Array.isArray(value) && value.length === 0) return true;
17205
+ return false;
17206
+ }
17207
+ function validateStringPlan(plan, value) {
17208
+ if (plan.constraints.some((constraint) => constraint.kind === "blockIconValue")) return plan.constraints.flatMap((constraint) => validateStringConstraint(plan, constraint, value));
17209
+ if (typeof value !== "string") return [issue(plan, "wrongType", { expected: "text" })];
17210
+ return plan.constraints.flatMap((constraint) => validateStringConstraint(plan, constraint, value));
17211
+ }
17212
+ function validateStringConstraint(plan, constraint, value) {
17213
+ switch (constraint.kind) {
17214
+ case "required": return [];
17215
+ case "textType":
17216
+ case "referenceText": return typeof value === "string" ? [] : [issue(plan, "wrongType", { expected: "text" })];
17217
+ case "blockIconValue": return isValidIconPickerValue(value) ? [] : [issue(plan, "wrongType", { expected: "text or icon" })];
17218
+ case "maxLength": return typeof value === "string" && value.length > constraint.maximum ? [issue(plan, "tooLong", { maximum: constraint.maximum })] : [];
17219
+ case "emailFormat": return typeof value === "string" && isCanonicalEmailValue(value) ? [] : [issue(plan, "invalidEmail")];
17220
+ case "phoneFormat": return typeof value === "string" && isCanonicalPhoneValue(value) ? [] : [issue(plan, "invalidPhone")];
17221
+ case "patternFormat": {
17222
+ if (typeof value !== "string") return [issue(plan, "wrongType", { expected: "text" })];
17223
+ const re = compilePattern(constraint.pattern);
17224
+ return re && !re.test(value) ? [issue(plan, "invalidPattern")] : [];
17225
+ }
17226
+ case "urlFormat": return typeof value === "string" && isCanonicalUrlValue(value, { allowRelative: constraint.allowRelative }) ? [] : [issue(plan, "invalidUrl")];
17227
+ case "dateFormat": return typeof value === "string" && isValidDateString(value) ? [] : [issue(plan, "invalidDate")];
17228
+ case "timeFormat": return typeof value === "string" && isValidTimeString(value) ? [] : [issue(plan, "invalidTime")];
17229
+ case "datetimeFormat": return typeof value === "string" && isValidDateTimeInstantString(value) ? [] : [issue(plan, "invalidDateTime")];
17230
+ case "slugFormat": return typeof value === "string" && SLUG_PATTERN.test(value) ? [] : [issue(plan, "invalidSlug")];
17231
+ default: return assertNever$1(constraint);
17232
+ }
17233
+ }
17234
+ function isValidIconPickerValue(value) {
17235
+ if (typeof value === "string") return true;
17236
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
17237
+ if (value.kind === "none") return true;
17238
+ return parseBlockIconValue(value).kind !== "none";
17239
+ }
17240
+ function validateNumberPlan(plan, value) {
17241
+ if (!isValidNumber(value)) return [issue(plan, "invalidNumber")];
17242
+ return plan.constraints.flatMap((constraint) => validateNumberConstraint(plan, constraint, Number(value)));
17243
+ }
17244
+ function validateNumberConstraint(plan, constraint, value) {
17245
+ switch (constraint.kind) {
17246
+ case "required":
17247
+ case "numberType": return [];
17248
+ case "numberMin": return value < constraint.minimum ? [issue(plan, "tooSmall", { minimum: constraint.minimum })] : [];
17249
+ case "numberMax": return value > constraint.maximum ? [issue(plan, "tooLarge", { maximum: constraint.maximum })] : [];
17250
+ default: return assertNever$1(constraint);
17251
+ }
17252
+ }
17253
+ function validateSelectPlan(plan, value) {
17254
+ const constraint = plan.constraints.find((candidate) => candidate.kind === "selectOption");
17255
+ if (!constraint || constraint.source.kind === "runtime") return [];
17256
+ return isValidSelectValue(value, constraint.source.values, constraint.multiple) ? [] : [issue(plan, "invalidOption")];
17257
+ }
17258
+ function validateRepeaterPlan(plan, value, ctx) {
17259
+ if (!Array.isArray(value)) return [issue(plan, "wrongType", { expected: "a list" })];
17260
+ return [...plan.constraints.flatMap((constraint) => {
17261
+ switch (constraint.kind) {
17262
+ case "required":
17263
+ case "arrayType": return [];
17264
+ case "minItems": return value.length < constraint.minimum ? [issue(plan, "tooFewItems", {
17265
+ minimum: constraint.minimum,
17266
+ itemLabel: "item"
17267
+ })] : [];
17268
+ case "maxItems": return value.length > constraint.maximum ? [issue(plan, "tooManyItems", {
17269
+ maximum: constraint.maximum,
17270
+ itemLabel: "item"
17271
+ })] : [];
17272
+ default: return assertNever$1(constraint);
17273
+ }
17274
+ }), ...value.flatMap((item, index) => validateRepeaterItem(plan, item, index, ctx))];
17275
+ }
17276
+ function validateRepeaterItem(plan, item, index, ctx) {
17277
+ if (!isPlainObject$2(item)) return [{
17278
+ kind: "wrongType",
17279
+ path: [...plan.path, index],
17280
+ fieldId: plan.fieldId,
17281
+ label: plan.label,
17282
+ expected: "an object"
17283
+ }];
17284
+ const itemType = item._type;
17285
+ const variant = plan.repeatedItemVariants ? plan.repeatedItemVariants.find((candidate) => candidate.typeId === itemType) : void 0;
17286
+ if (plan.repeatedItemVariants && !variant) return [{
17287
+ kind: "invalidOption",
17288
+ path: [
17289
+ ...plan.path,
17290
+ index,
17291
+ "_type"
17292
+ ],
17293
+ fieldId: "_type",
17294
+ label: `${plan.label} item type`
17295
+ }];
17296
+ const fields = variant?.fields ?? plan.repeatedItemPlan;
17297
+ if (!fields) return [];
17298
+ return fields.flatMap((childPlan) => {
17299
+ return validateNormalizedFieldValue(materializeRepeaterItemPlan(childPlan, plan.path, index), item[childPlan.fieldId], ctx);
17300
+ });
17301
+ }
17302
+ function validateGroupPlan(plan, value, ctx) {
17303
+ if (!isPlainObject$2(value)) return [issue(plan, "wrongType", { expected: "an object" })];
17304
+ if (plan.childSelection.kind === "discriminatedTabs") {
17305
+ const selectedType = value[plan.childSelection.activeTabField];
17306
+ if (selectedType !== void 0 && selectedType !== null && !plan.childSelection.tabs.some((tab) => tab.typeId === selectedType)) return [{
17307
+ kind: "invalidOption",
17308
+ path: [...plan.path, plan.childSelection.activeTabField],
17309
+ fieldId: plan.childSelection.activeTabField,
17310
+ label: `${plan.label} type`
17311
+ }];
17312
+ }
17313
+ const childCtx = childValidationContext(plan, ctx);
17314
+ const childIssues = selectedGroupChildPlans(plan, value).flatMap((childPlan) => validateNormalizedFieldValue(childPlan, value[childPlan.fieldId], childCtx));
17315
+ if (childIssues.length > 0) return childIssues;
17316
+ if (plan.valueContract === "boxBackground" && !boxBackgroundSchema.safeParse(value).success) return [issue(plan, "invalidBoxBackground")];
17317
+ return [];
17318
+ }
17319
+ function childValidationContext(plan, ctx) {
17320
+ return plan.allowNullChildren ? {
17321
+ ...ctx,
17322
+ allowNull: true
17323
+ } : ctx;
17324
+ }
17325
+ function materializeRepeaterItemPlan(plan, parentPath, index) {
17326
+ return rebaseFieldPlanPath(plan, [...parentPath, 0], [...parentPath, index]);
17327
+ }
17328
+ function rebaseFieldPlanPath(plan, fromPrefix, toPrefix) {
17329
+ const path = rebaseFieldPath(plan.path, fromPrefix, toPrefix);
17330
+ switch (plan.kind) {
17331
+ case "group": return {
17332
+ ...plan,
17333
+ path,
17334
+ childSelection: plan.childSelection.kind === "all" ? {
17335
+ kind: "all",
17336
+ fields: plan.childSelection.fields.map((childPlan) => rebaseFieldPlanPath(childPlan, fromPrefix, toPrefix))
17337
+ } : {
17338
+ ...plan.childSelection,
17339
+ tabs: plan.childSelection.tabs.map((tab) => ({
17340
+ ...tab,
17341
+ fields: tab.fields.map((childPlan) => rebaseFieldPlanPath(childPlan, fromPrefix, toPrefix))
17342
+ }))
17343
+ }
17344
+ };
17345
+ case "repeater": return {
17346
+ ...plan,
17347
+ path,
17348
+ ...plan.repeatedItemVariants ? { repeatedItemVariants: plan.repeatedItemVariants.map((variant) => ({
17349
+ ...variant,
17350
+ fields: variant.fields.map((fieldPlan) => rebaseFieldPlanPath(fieldPlan, fromPrefix, toPrefix))
17351
+ })) } : {},
17352
+ ...plan.repeatedItemPlan ? { repeatedItemPlan: plan.repeatedItemPlan.map((fieldPlan) => rebaseFieldPlanPath(fieldPlan, fromPrefix, toPrefix)) } : {}
17353
+ };
17354
+ case "string":
17355
+ case "number":
17356
+ case "boolean":
17357
+ case "richText":
17358
+ case "inlineText":
17359
+ case "media":
17360
+ case "link":
17361
+ case "select":
17362
+ case "passthrough": return {
17363
+ ...plan,
17364
+ path
17365
+ };
17366
+ default: return assertNever$1(plan);
17367
+ }
17368
+ }
17369
+ function validateInlineTextPlan(plan, value) {
17370
+ const parsed = parseInlineTextFromUnknown(value);
17371
+ if (!parsed.ok) return [issue(plan, "invalidInlineText")];
17372
+ const text = inlineTextToPlainText(parsed.value);
17373
+ const lengthIssue = plan.constraints.find((constraint) => constraint.kind === "maxPlainTextLength");
17374
+ if (lengthIssue && text.length > lengthIssue.maximum) return [issue(plan, "tooLong", { maximum: lengthIssue.maximum })];
17375
+ return [];
17376
+ }
17377
+ function rebaseFieldPath(path, fromPrefix, toPrefix) {
17378
+ if (!startsWithFieldPath(path, fromPrefix)) throw new Error(`Cannot rebase field path ${formatFieldPath(path)} from ${formatFieldPath(fromPrefix)}`);
17379
+ return [...toPrefix, ...path.slice(fromPrefix.length)];
17380
+ }
17381
+ function startsWithFieldPath(path, prefix) {
17382
+ return prefix.every((segment, index) => path[index] === segment);
17383
+ }
17384
+ function issue(plan, kind, extra) {
17385
+ return {
17386
+ kind,
17387
+ path: plan.path,
17388
+ fieldId: plan.fieldId,
17389
+ label: plan.label,
17390
+ ...extra ?? {}
17391
+ };
17392
+ }
17393
+ function requiredIssue(plan, value) {
17394
+ if (typeof value === "string") return issue(plan, "required", { emptyOrigin: "string" });
17395
+ if (Array.isArray(value)) return issue(plan, "required", { emptyOrigin: "array" });
17396
+ return issue(plan, "required");
17397
+ }
17398
+ function fieldPlanToZod(plan, options = {}) {
17399
+ const ctx = validationContext(options);
17400
+ return z.any().transform((value) => normalizeFieldValue(plan, value, ctx)).superRefine((value, zodCtx) => {
17401
+ for (const validationIssue of validateNormalizedFieldValue(plan, value, ctx)) addFieldIssueToZodContext(zodCtx, validationIssue, plan.path);
17402
+ });
17403
+ }
17404
+ function fieldPresenceForPlan(plan) {
17405
+ return plan.required ? { kind: "required" } : { kind: "optional" };
17406
+ }
17407
+ function fieldPlanToObjectPropertyZod(plan, options = {}) {
17408
+ const schema = fieldPlanToZod(plan, options);
17409
+ const presence = fieldPresenceForPlan(plan);
17410
+ switch (presence.kind) {
17411
+ case "required": return schema;
17412
+ case "optional": return schema.optional();
17413
+ default: return assertNever$1(presence);
17414
+ }
17415
+ }
17416
+ /** Validate stored field values without applying editor/write normalization. */
17417
+ function fieldPlanToStoredObjectPropertyZod(plan) {
17418
+ const ctx = strictValidationContext({ allowNull: false });
17419
+ const schema = z.any().superRefine((value, zodCtx) => {
17420
+ for (const validationIssue of validateNormalizedFieldValue(plan, value, ctx)) addFieldIssueToZodContext(zodCtx, validationIssue, plan.path);
17421
+ });
17422
+ return plan.required ? schema : schema.optional();
17423
+ }
17424
+ function addFieldIssueToZodContext(zodCtx, validationIssue, rootPath) {
17425
+ const message = fieldIssueToMessage(validationIssue);
17426
+ const path = relativeZodIssuePath(validationIssue.path, rootPath);
17427
+ const issuePath = path.length > 0 ? { path: toZodIssuePath(path) } : {};
17428
+ switch (validationIssue.kind) {
17429
+ case "required":
17430
+ if (validationIssue.emptyOrigin) {
17431
+ zodCtx.addIssue({
17432
+ ...issuePath,
17433
+ code: "too_small",
17434
+ origin: validationIssue.emptyOrigin,
17435
+ minimum: 1,
17436
+ inclusive: true,
17437
+ message
17438
+ });
17439
+ return;
17440
+ }
17441
+ zodCtx.addIssue({
17442
+ ...issuePath,
17443
+ code: "invalid_type",
17444
+ expected: "nonoptional",
17445
+ message
17446
+ });
17447
+ return;
17448
+ case "tooLong":
17449
+ zodCtx.addIssue({
17450
+ ...issuePath,
17451
+ code: "too_big",
17452
+ origin: "string",
17453
+ maximum: validationIssue.maximum,
17454
+ inclusive: true,
17455
+ message
17456
+ });
17457
+ return;
17458
+ case "invalidOption":
17459
+ zodCtx.addIssue({
17460
+ ...issuePath,
17461
+ code: "invalid_value",
17462
+ values: [],
17463
+ message
17464
+ });
17465
+ return;
17466
+ case "wrongType":
17467
+ case "invalidEmail":
17468
+ case "invalidPhone":
17469
+ case "invalidPattern":
17470
+ case "invalidUrl":
17471
+ case "invalidDate":
17472
+ case "invalidTime":
17473
+ case "invalidDateTime":
17474
+ case "invalidNumber":
17475
+ case "tooSmall":
17476
+ case "tooLarge":
17477
+ case "invalidSlug":
17478
+ case "tooFewItems":
17479
+ case "tooManyItems":
17480
+ case "invalidRichText":
17481
+ case "invalidInlineText":
17482
+ case "invalidMedia":
17483
+ case "invalidBoxBackground":
17484
+ case "invalidLink":
17485
+ case "invalidReference":
17486
+ zodCtx.addIssue({
17487
+ ...issuePath,
17488
+ code: "custom",
17489
+ message
17490
+ });
17491
+ return;
17492
+ default: assertNever$1(validationIssue);
17493
+ }
17494
+ }
17495
+ function relativeZodIssuePath(path, rootPath) {
17496
+ return startsWithFieldPath(path, rootPath) ? path.slice(rootPath.length) : path;
17497
+ }
17498
+ function compilePattern(pattern) {
17499
+ try {
17500
+ return new RegExp(pattern);
17501
+ } catch {
17502
+ return null;
17503
+ }
17504
+ }
17505
+ function isValidNumber(value) {
17506
+ return typeof value === "number" && Number.isFinite(value);
17507
+ }
17508
+ function isCanonicalEmailValue(value) {
17509
+ return z.email().safeParse(value).success;
17510
+ }
17511
+ function isCanonicalPhoneValue(value) {
17512
+ return TEL_RE.test(value);
17513
+ }
17514
+ function isCanonicalUrlValue(value, options = {}) {
17515
+ if (!value) return false;
17516
+ if (options.allowRelative === true && value.startsWith("/")) return true;
17517
+ try {
17518
+ const parsed = new URL(value);
17519
+ return ALLOWED_URL_PROTOCOLS.includes(parsed.protocol);
17520
+ } catch {
17521
+ return false;
17522
+ }
17523
+ }
17524
+ function isValidDateString(value) {
17525
+ const match = DATE_PATTERN.exec(value);
17526
+ if (!match?.groups) return false;
17527
+ return isValidCalendarDate(Number(match.groups.year), Number(match.groups.month), Number(match.groups.day));
17528
+ }
17529
+ function isValidTimeString(value) {
17530
+ return TIME_PATTERN.test(value);
17531
+ }
17532
+ function isValidDateTimeInstantString(value) {
17533
+ const match = DATETIME_PATTERN.exec(value);
17534
+ if (!match?.groups) return false;
17535
+ const datePart = match.groups.date;
17536
+ if (!datePart || !isValidDateString(datePart)) return false;
17537
+ return !Number.isNaN(Date.parse(value));
17538
+ }
17539
+ function isValidCalendarDate(year, month, day) {
17540
+ if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) return false;
17541
+ if (year < 1 || month < 1 || month > 12) return false;
17542
+ return day >= 1 && day <= daysInMonth(year, month);
17543
+ }
17544
+ function daysInMonth(year, month) {
17545
+ switch (month) {
17546
+ case 2: return isLeapYear(year) ? 29 : 28;
17547
+ case 4:
17548
+ case 6:
17549
+ case 9:
17550
+ case 11: return 30;
17551
+ case 1:
17552
+ case 3:
17553
+ case 5:
17554
+ case 7:
17555
+ case 8:
17556
+ case 10:
17557
+ case 12: return 31;
17558
+ default: return 0;
17559
+ }
17560
+ }
17561
+ function isLeapYear(year) {
17562
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
17563
+ }
17564
+ function isValidSelectValue(value, values, multiple) {
17565
+ if (multiple) return Array.isArray(value) && value.every((item) => typeof item === "string" && values.includes(item));
17566
+ return typeof value === "string" && values.includes(value);
17567
+ }
17568
+ function isEmptyValue(value) {
17569
+ if (value === null || value === void 0) return true;
17570
+ if (typeof value === "string" && value.trim() === "") return true;
17571
+ if (Array.isArray(value) && value.length === 0) return true;
17572
+ if (isEmptyRichTextDoc(value)) return true;
17573
+ return false;
17574
+ }
17575
+ function isPlainObject$2(value) {
17576
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
17577
+ }
17578
+ function isUnknownArray(value) {
17579
+ return Array.isArray(value);
17580
+ }
17581
+ function isValidRichTextDoc(value) {
17582
+ return isRichTextDoc(value);
17583
+ }
17584
+ function isEmptyRichTextDoc(value) {
17585
+ if (!isPlainObject$2(value)) return false;
17586
+ if (value.type !== "doc") return false;
17587
+ const content = value.content;
17588
+ if (!Array.isArray(content) || content.length === 0) return true;
17589
+ if (content.length !== 1) return false;
17590
+ const first = content[0];
17591
+ return first?.type === "paragraph" && (!Array.isArray(first.content) || first.content.length === 0);
17592
+ }
17593
+ //#endregion
17594
+ //#region ../blocks/src/system/manifest/registry.ts
17595
+ const REGISTRY_SYMBOL$1 = Symbol.for("@riverbankcms/blocks/manifest-registry");
17596
+ const isBlocksTestRuntime$1 = typeof process !== "undefined" && typeof process.env !== "undefined" && process.env.RIVERBANK_BLOCKS_TEST === "1";
17597
+ const globalScope$1 = globalThis;
17598
+ const manifestStore = isBlocksTestRuntime$1 ? /* @__PURE__ */ new Map() : globalScope$1[REGISTRY_SYMBOL$1] ?? (globalScope$1[REGISTRY_SYMBOL$1] = /* @__PURE__ */ new Map());
17599
+ function registerManifest(manifest) {
17600
+ manifestStore.set(manifest.name, manifest);
17601
+ return manifest;
17602
+ }
17603
+ //#endregion
17604
+ //#region ../blocks/src/system/manifest/validation.ts
17605
+ const compiledManifestContentContract = Symbol("compiledManifestContentContract");
17606
+ /**
17607
+ * Preprocess content by converting empty values to undefined.
17608
+ * Used in draft mode to allow required fields to be temporarily empty.
17609
+ */
17610
+ function preprocessEmptyValues(content) {
17611
+ if (!content || typeof content !== "object" || Array.isArray(content)) return {};
17612
+ const result = {};
17613
+ for (const [key, value] of Object.entries(content)) result[key] = isEmptyValue(value) ? void 0 : value;
17614
+ return result;
17615
+ }
17616
+ /**
17617
+ * Compiles editor-facing manifest structure into the presentation-independent
17618
+ * validation contract used by every manifest-content trust transition.
17619
+ */
17620
+ function compileManifestContentContract(manifestOrFields) {
17621
+ const plans = derivePlansFromManifestOrFields(manifestOrFields);
17622
+ const strictOptions = { allowNull: false };
17623
+ const strictStoredSchema = deriveStrictStoredZodFromPlans(plans);
17624
+ return Object.freeze({ [compiledManifestContentContract]: Object.freeze({
17625
+ plans,
17626
+ strictStoredSchema,
17627
+ writeSchema: deriveNormalizedContentZodFromPlans(plans, strictOptions, normalizeManifestContentForWrite).transform((value) => canonicalizeObjectForWrite(plans, value)),
17628
+ draftSchema: deriveNormalizedContentZodFromPlans(plans, {
17629
+ allowNull: true,
17630
+ allowIncomplete: true,
17631
+ mode: "draft"
17632
+ }, normalizeStoredContentValues),
17633
+ renderSchema: strictStoredSchema,
17634
+ findNonPortableMediaPaths: (content) => findNonPortableMediaPaths(plans, content)
17635
+ }) });
17636
+ }
17637
+ /**
17638
+ * Re-admits renderable content after a transport boundary has erased its
17639
+ * compile-time proof. Unlike write admission, this never canonicalizes or
17640
+ * grants a stored proof, so hydrated media cannot flow back to persistence.
17641
+ */
17642
+ function admitTransportedRenderableManifestContent(contract, content) {
17643
+ const parsed = contract[compiledManifestContentContract].renderSchema.safeParse(content);
17644
+ return parsed.success ? {
17645
+ ok: true,
17646
+ value: content
17647
+ } : {
17648
+ ok: false,
17649
+ error: zodAdmissionError(parsed.error)
17650
+ };
17651
+ }
17652
+ function deriveNormalizedContentZodFromPlans(plans, options, normalize) {
17653
+ const isDraft = options.mode === "draft";
17654
+ const baseSchema = deriveContentZodFromPlans(plans, options);
17655
+ if (isDraft) return z.preprocess((content) => preprocessEmptyValues(normalize(plans, content, options)), baseSchema);
17656
+ return z.preprocess((content) => normalize(plans, content, options), baseSchema);
17657
+ }
17658
+ function deriveContentZodFromPlans(plans, options) {
17659
+ const shape = {};
17660
+ for (const plan of plans) shape[plan.fieldId] = fieldPlanToObjectPropertyZod(plan, options);
17661
+ return z.object(shape).catchall(z.unknown()).passthrough();
17662
+ }
17663
+ function deriveStrictStoredZodFromPlans(plans) {
17664
+ const shape = {};
17665
+ for (const plan of plans) shape[plan.fieldId] = fieldPlanToStoredObjectPropertyZod(plan);
17666
+ return z.object(shape).catchall(z.unknown()).passthrough();
17667
+ }
17668
+ function derivePlansFromManifestOrFields(manifestOrFields) {
17669
+ return deriveFieldValidationPlans(Array.isArray(manifestOrFields) ? manifestOrFields : manifestOrFields.fields ?? []);
17670
+ }
17671
+ function zodAdmissionError(error) {
17672
+ return {
17673
+ kind: "invalid_shape",
17674
+ issues: zodAdmissionIssues(error)
17675
+ };
17676
+ }
17677
+ function zodAdmissionIssues(error) {
17678
+ return error.issues.map((issue) => ({
17679
+ code: issue.code,
17680
+ path: issue.path.map((segment) => typeof segment === "number" ? segment : String(segment)),
17681
+ message: issue.message
17682
+ }));
17683
+ }
17684
+ function findNonPortableMediaPaths(plans, content) {
17685
+ const paths = /* @__PURE__ */ new Set();
17686
+ collectNonPortablePlannedMedia(plans, content, [], paths);
17687
+ return [...paths].sort();
17688
+ }
17689
+ function collectNonPortablePlannedMedia(plans, content, parentPath, paths) {
17690
+ if (!isPlainRecord(content)) return;
17691
+ for (const plan of plans) {
17692
+ if (!(plan.fieldId in content)) continue;
17693
+ const value = content[plan.fieldId];
17694
+ const path = [...parentPath, plan.fieldId];
17695
+ switch (plan.kind) {
17696
+ case "media":
17697
+ if (isPlainRecord(value) && hasEnvironmentSpecificMediaField(value)) paths.add(formatFieldPath(path));
17698
+ break;
17699
+ case "richText":
17700
+ collectNonPortableRichTextImages(value, path, paths);
17701
+ break;
17702
+ case "group":
17703
+ collectNonPortablePlannedMedia(selectedGroupChildPlans(plan, value), value, path, paths);
17704
+ break;
17705
+ case "repeater":
17706
+ collectNonPortableRepeaterMedia(plan, value, path, paths);
17707
+ break;
17708
+ case "string":
17709
+ case "number":
17710
+ case "boolean":
17711
+ case "inlineText":
17712
+ case "link":
17713
+ case "select":
17714
+ case "passthrough": break;
17715
+ }
17716
+ }
17717
+ }
17718
+ function collectNonPortableRepeaterMedia(plan, value, path, paths) {
17719
+ if (!Array.isArray(value)) return;
17720
+ value.forEach((item, index) => {
17721
+ if (!isPlainRecord(item)) return;
17722
+ const childPlans = plan.repeatedItemVariants ? plan.repeatedItemVariants.find((variant) => variant.typeId === item._type)?.fields : plan.repeatedItemPlan;
17723
+ if (childPlans) collectNonPortablePlannedMedia(childPlans, item, [...path, index], paths);
17724
+ });
17725
+ }
17726
+ function collectNonPortableRichTextImages(value, path, paths) {
17727
+ const images = [];
17728
+ walkAndCollectImageAttrs(value, images);
17729
+ const prefix = formatFieldPath(path);
17730
+ for (const reference of images) if (hasEnvironmentSpecificMediaField(reference.attrs)) paths.add(reference.location ? `${prefix}.${reference.location}` : prefix);
17731
+ }
17732
+ function isPlainRecord(value) {
17733
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17734
+ }
17735
+ function hasEnvironmentSpecificMediaField(value) {
17736
+ return "assetId" in value || "storageBucket" in value || "storagePath" in value || "src" in value || "mimeType" in value || looksLikeUuid(value.id);
17737
+ }
15805
17738
  //#endregion
15806
17739
  //#region ../blocks/src/system/designCapabilities.ts
15807
17740
  function declareBlockDesignCapability(capability) {
@@ -23536,26 +25469,6 @@ const headerLogoTextVisibleTransform = {
23536
25469
  return resolveHeaderBrandDisplay(asPartialObject(value)) !== "logo-only";
23537
25470
  }
23538
25471
  };
23539
- /**
23540
- * Selects which classic-layout nav row should render.
23541
- *
23542
- * - `'inline'` → classic variant with the historical logo-left / nav-right row
23543
- * - `'centered'`→ classic variant with a centred (flex-1) nav and a right CTA
23544
- * - `'other'` → not a classic variant (centered/floating handle it)
23545
- *
23546
- * Consumed by the site-header block's `$when` gates so the two classic rows are
23547
- * mutually exclusive without introducing a new top-level header variant.
23548
- */
23549
- const classicNavModeTransform = {
23550
- id: "layout.classicNavMode",
23551
- kind: "formatter",
23552
- summary: "Resolve which classic-layout nav row to render",
23553
- run: (value) => {
23554
- const header = asPartialObject(value);
23555
- if (header?.variant !== "classic") return "other";
23556
- return header?.navPlacement === "center" ? "centered" : "inline";
23557
- }
23558
- };
23559
25472
  const maxWidthClassTransform = {
23560
25473
  id: "layout.maxWidthClass",
23561
25474
  kind: "string",
@@ -23661,7 +25574,6 @@ const headerRootClassTransform = {
23661
25574
  if (isHeaderScrollRuntimeEnabled(content)) classes.push(config.shrink);
23662
25575
  const variant = header?.variant;
23663
25576
  if (variant && headerVariantValues.includes(variant)) classes.push(`header-variant-${variant}`);
23664
- if (header?.barFrame === "inset-shell" && variant === "classic") classes.push("header-bar-frame-inset-shell");
23665
25577
  return joinClasses(classes);
23666
25578
  }
23667
25579
  };
@@ -23869,7 +25781,6 @@ const layoutTransforms = [
23869
25781
  headerLogoClassTransform,
23870
25782
  headerLogoMediaClassTransform,
23871
25783
  headerLogoTextVisibleTransform,
23872
- classicNavModeTransform,
23873
25784
  footerRootClassTransform,
23874
25785
  headerRootStyleTransform,
23875
25786
  headerSectionBackgroundTransform,
@@ -23945,40 +25856,22 @@ const mediaTransforms = [{
23945
25856
  }
23946
25857
  }];
23947
25858
  //#endregion
23948
- //#region ../blocks/src/lib/typeGuards.ts
23949
- /**
23950
- * Strict record guard: excludes null, primitives, and arrays.
23951
- */
23952
- function isRecord$8(value) {
23953
- return typeof value === "object" && value !== null && !Array.isArray(value);
23954
- }
23955
- /**
23956
- * Non-null object guard: includes arrays for behavior parity with legacy helpers.
23957
- *
23958
- * NOTE: This intentionally narrows to UnknownRecord for compatibility with existing
23959
- * callsites even though arrays pass at runtime. Follow-up cleanup should replace
23960
- * this API with a more precise predicate type and migrate consumers.
23961
- */
23962
- function isObjectRecord(value) {
23963
- return typeof value === "object" && value !== null;
23964
- }
23965
- //#endregion
23966
25859
  //#region ../blocks/src/system/transforms/registry/content.ts
23967
25860
  const supportedRichTextTokenSchema = z.enum(["year", "site_name"]);
23968
25861
  const interpolateRichTextTokensOptionsSchema = z.object({ tokens: z.array(supportedRichTextTokenSchema).default(["year", "site_name"]) });
23969
25862
  const richTextTokenPattern = /\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g;
23970
25863
  function getSiteName(context) {
23971
25864
  const viewModel = context.viewModel;
23972
- if (!isRecord$8(viewModel)) return null;
25865
+ if (!isRecord$10(viewModel)) return null;
23973
25866
  const site = viewModel.site;
23974
- if (isRecord$8(site) && typeof site.title === "string") {
25867
+ if (isRecord$10(site) && typeof site.title === "string") {
23975
25868
  const title = site.title.trim();
23976
25869
  return title.length > 0 ? title : null;
23977
25870
  }
23978
25871
  const root = viewModel.$root;
23979
- if (isRecord$8(root)) {
25872
+ if (isRecord$10(root)) {
23980
25873
  const rootSite = root.site;
23981
- if (isRecord$8(rootSite) && typeof rootSite.title === "string") {
25874
+ if (isRecord$10(rootSite) && typeof rootSite.title === "string") {
23982
25875
  const title = rootSite.title.trim();
23983
25876
  if (title.length > 0) return title;
23984
25877
  }
@@ -24000,7 +25893,7 @@ function replaceTokenText(input, allowedTokens, tokenValues) {
24000
25893
  });
24001
25894
  }
24002
25895
  function visitRichTextNode(node, allowedTokens, tokenValues) {
24003
- if (!isRecord$8(node)) return node;
25896
+ if (!isRecord$10(node)) return node;
24004
25897
  let changed = false;
24005
25898
  const nextNode = { ...node };
24006
25899
  if (nextNode.type === "text" && typeof nextNode.text === "string") {
@@ -24021,7 +25914,7 @@ function visitRichTextNode(node, allowedTokens, tokenValues) {
24021
25914
  return changed ? nextNode : node;
24022
25915
  }
24023
25916
  function interpolateRichTextTokens(value, options, context) {
24024
- if (!isRecord$8(value)) return value;
25917
+ if (!isRecord$10(value)) return value;
24025
25918
  return visitRichTextNode(value, new Set(options.tokens), {
24026
25919
  year: String((/* @__PURE__ */ new Date()).getFullYear()),
24027
25920
  site_name: getSiteName(context)
@@ -24269,18 +26162,7 @@ const classicLayout = inline({
24269
26162
  logoRow,
24270
26163
  createNavRow(`rb-ml-auto ${desktopOnlyDisplay("flex")} rb-gap-6`),
24271
26164
  headerCta
24272
- ], whenTransformed("$root.theme.header", "layout.classicNavMode", "inline"));
24273
- const classicCenteredNavLayout = inline({
24274
- className: bindProp("$root.theme.header.maxWidth", {
24275
- transforms: pipe(tx("layout.maxWidthClass", { base: `rb-header-layout ${desktopOnlyDisplay("flex")} rb-w-full rb-items-center rb-gap-6` })),
24276
- fallback: `rb-container rb-header-layout ${desktopOnlyDisplay("flex")} rb-w-full rb-items-center rb-gap-6`
24277
- }),
24278
- align: "center"
24279
- }, [
24280
- logoRow,
24281
- createNavRow(`rb-flex-1 rb-justify-center ${desktopOnlyDisplay("flex")} rb-gap-6`, "center"),
24282
- headerCta
24283
- ], whenTransformed("$root.theme.header", "layout.classicNavMode", "centered"));
26165
+ ], when("$root.theme.header.variant", { equals: "classic" }));
24284
26166
  const centeredLayout = stack({
24285
26167
  gap: "md",
24286
26168
  align: "center",
@@ -24345,7 +26227,6 @@ const siteHeaderBlockDefinition = { manifest: createBlockManifest({
24345
26227
  }, [
24346
26228
  mobileHeaderBar,
24347
26229
  classicLayout,
24348
- classicCenteredNavLayout,
24349
26230
  centeredLayout,
24350
26231
  floatingLayout,
24351
26232
  mobileOverlay
@@ -26299,83 +28180,6 @@ const weeklySlotAvailabilityBlockDefinition = {
26299
28180
  } }
26300
28181
  };
26301
28182
  //#endregion
26302
- //#region ../blocks/src/lib/media.ts
26303
- const PUBLIC_DOWNLOAD_MEDIA_TYPES = [
26304
- "document",
26305
- "spreadsheet",
26306
- "archive",
26307
- "audio"
26308
- ];
26309
- //#endregion
26310
- //#region ../blocks/src/system/manifest/richTextSchema.ts
26311
- /**
26312
- * Shared Rich Text Schema
26313
- *
26314
- * This module provides a single source of truth for TipTap rich text validation
26315
- * across both the block-form editor and the block rendering system.
26316
- *
26317
- * Rich-text fields use one canonical root shape:
26318
- * `{ type: 'doc', content: [...] }`.
26319
- */
26320
- /**
26321
- * TipTap document node schema (recursive structure)
26322
- *
26323
- * Supports the full TipTap document structure including:
26324
- * - type: Node type (e.g., 'doc', 'paragraph', 'text')
26325
- * - content: Nested array of child nodes
26326
- * - text: Text content for text nodes
26327
- * - attrs: Node attributes (e.g., alignment, color)
26328
- * - marks: Text formatting marks (e.g., bold, italic)
26329
- */
26330
- const TipTapMarkSchema = z.object({
26331
- type: z.string().min(1),
26332
- attrs: z.record(z.string(), z.unknown()).optional()
26333
- }).strict();
26334
- const TipTapNodeSchema = z.lazy(() => z.object({
26335
- type: z.string().min(1),
26336
- content: z.array(TipTapNodeSchema).optional(),
26337
- text: z.string().optional(),
26338
- attrs: z.record(z.string(), z.unknown()).optional(),
26339
- marks: z.array(TipTapMarkSchema).optional()
26340
- }).strict().superRefine((node, ctx) => {
26341
- if (node.type === "doc") ctx.addIssue({
26342
- code: "custom",
26343
- path: ["type"],
26344
- message: "Document nodes are only valid at the rich-text root"
26345
- });
26346
- if (node.type === "text") {
26347
- if (node.text === void 0 || node.text.length === 0) ctx.addIssue({
26348
- code: "custom",
26349
- path: ["text"],
26350
- message: "Text nodes require a non-empty text value"
26351
- });
26352
- if (node.content !== void 0) ctx.addIssue({
26353
- code: "custom",
26354
- path: ["content"],
26355
- message: "Text nodes cannot contain child nodes"
26356
- });
26357
- return;
26358
- }
26359
- if (node.text !== void 0) ctx.addIssue({
26360
- code: "custom",
26361
- path: ["text"],
26362
- message: "Only text nodes can contain a text value"
26363
- });
26364
- }));
26365
- /**
26366
- * Canonical schema for a TipTap rich-text document root.
26367
- *
26368
- * Stricter than {@link TipTapNodeSchema}: `type` is literally `'doc'` and
26369
- * `content` is required (possibly empty). This is the shape every producer
26370
- * (dummy content, editor output, migrations) must emit, and the shape every
26371
- * non-legacy consumer can rely on.
26372
- *
26373
- */
26374
- const RichTextDocSchema = z.object({
26375
- type: z.literal("doc"),
26376
- content: z.array(TipTapNodeSchema)
26377
- }).strict();
26378
- //#endregion
26379
28183
  //#region ../blocks/src/system/blocks/shared/bookingPaymentTerms.ts
26380
28184
  const bookingDepositRefundPolicySchema$1 = z.enum(["refundable", "non_refundable"]);
26381
28185
  const bookingInstalmentPlanSchema$1 = z.union([
@@ -26799,6 +28603,7 @@ const publicWholeSeriesPriceDisplaySchema = z.discriminatedUnion("kind", [
26799
28603
  freePriceDisplaySchema,
26800
28604
  exactPriceDisplaySchema,
26801
28605
  fromPriceDisplaySchema,
28606
+ rangePriceDisplaySchema,
26802
28607
  unavailablePriceDisplaySchema
26803
28608
  ]);
26804
28609
  z.discriminatedUnion("kind", [
@@ -27370,11 +29175,11 @@ function planEventRegistrationScope(input) {
27370
29175
  }
27371
29176
  //#endregion
27372
29177
  //#region ../blocks/src/system/runtime/nodes/events/shared/destination.ts
27373
- function isRecord$7(value) {
29178
+ function isRecord$9(value) {
27374
29179
  return typeof value === "object" && value !== null && !Array.isArray(value);
27375
29180
  }
27376
29181
  function isRuntimeEventDestination(value) {
27377
- if (!isRecord$7(value) || typeof value.kind !== "string") return false;
29182
+ if (!isRecord$9(value) || typeof value.kind !== "string") return false;
27378
29183
  if (value.kind === "native") return (value.mode === "cms-managed" || value.mode === "system-managed") && typeof value.path === "string";
27379
29184
  return value.kind === "external" && typeof value.url === "string" && typeof value.label === "string";
27380
29185
  }
@@ -27437,15 +29242,15 @@ function resolveRuntimeEventPricing(event) {
27437
29242
  function readCurrency(value) {
27438
29243
  return typeof value === "string" && value.trim().length > 0 ? value : DEFAULT_EVENT_CURRENCY;
27439
29244
  }
27440
- function isRecord$6(value) {
29245
+ function isRecord$8(value) {
27441
29246
  return typeof value === "object" && value !== null && !Array.isArray(value);
27442
29247
  }
27443
29248
  function isPublicEventPricing(value) {
27444
- if (!isRecord$6(value) || typeof value.kind !== "string") return false;
29249
+ if (!isRecord$8(value) || typeof value.kind !== "string") return false;
27445
29250
  return value.kind === "drop_in" || value.kind === "free_registration" || value.kind === "single_paid" || value.kind === "sliding_scale" || value.kind === "ticket_types" || value.kind === "whole_series" || value.kind === "external";
27446
29251
  }
27447
29252
  function isPublicEventTicketType(value) {
27448
- return isRecord$6(value) && typeof value.id === "string";
29253
+ return isRecord$8(value) && typeof value.id === "string";
27449
29254
  }
27450
29255
  function readTicketTypePriceDisplay(value, currency) {
27451
29256
  const display = readLegacyPriceDisplay(value, currency);
@@ -27455,7 +29260,7 @@ function readTicketTypePriceDisplay(value, currency) {
27455
29260
  } : display;
27456
29261
  }
27457
29262
  function readLegacyPriceDisplay(value, currency) {
27458
- if (!isRecord$6(value) || typeof value.kind !== "string") return {
29263
+ if (!isRecord$8(value) || typeof value.kind !== "string") return {
27459
29264
  kind: "unavailable",
27460
29265
  currency
27461
29266
  };
@@ -27507,7 +29312,7 @@ function readLegacyPriceDisplay(value, currency) {
27507
29312
  kind: "unavailable",
27508
29313
  currency: displayCurrency
27509
29314
  };
27510
- case "whole_series": return isRecord$6(value.display) ? {
29315
+ case "whole_series": return isRecord$8(value.display) ? {
27511
29316
  kind: "whole_series",
27512
29317
  display: readCoursePriceDisplay(value.display, displayCurrency)
27513
29318
  } : {
@@ -27902,13 +29707,16 @@ function buildEventRegistrationShellStepLabels(source) {
27902
29707
  }) : false).map((step) => shellLabelForRegistrationStep(step, scopedEvents));
27903
29708
  }
27904
29709
  function scopeEventRegistrationEvents(source) {
29710
+ return toRegistrationEvents(scopeEventRegistrationSourceEvents(source));
29711
+ }
29712
+ function scopeEventRegistrationSourceEvents(source) {
27905
29713
  const scope = planEventRegistrationScope({
27906
- events: toRegistrationEvents(source.events ?? []),
29714
+ events: source.events ?? [],
27907
29715
  wholeSeriesOffering: source.wholeSeriesOffering,
27908
29716
  occurrenceContext: source.occurrenceContext,
27909
29717
  subject: source.subject
27910
29718
  });
27911
- return scope.kind === "invalidSelectedRun" ? [] : scope.events;
29719
+ return scope.kind === "invalidSelectedRun" ? [] : [...scope.events];
27912
29720
  }
27913
29721
  function shellLabelForRegistrationStep(step, events) {
27914
29722
  switch (step) {
@@ -27940,36 +29748,39 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
27940
29748
  source: createZodCodec(eventRegistrationSourceSchema),
27941
29749
  render: createZodCodec(eventRegistrationRenderSchema)
27942
29750
  },
27943
- buildRender: (source) => ({
27944
- display: buildEventRegistrationDisplay(source),
27945
- hydration: {
27946
- occurrenceContext: source.occurrenceContext ?? null,
27947
- subject: source.subject ?? null,
27948
- events: source.events ?? null,
27949
- wholeSeriesOfferings: source.wholeSeriesOfferings ?? null,
27950
- wholeSeriesOffering: resolveWholeSeriesOffering(source),
27951
- maxTickets: source.maxTickets ?? void 0,
27952
- showVenue: source.showVenue,
27953
- showCapacity: source.showCapacity,
27954
- successMessage: source.successMessage,
27955
- buttonVariant: source.buttonVariant,
27956
- overlayEnabled: source.overlayEnabled,
27957
- spamProtectionEnabled: source.spamProtectionEnabled,
27958
- supportEmail: source.supportEmail,
27959
- sitePasses: source.sitePasses ?? null,
27960
- siteMemberships: source.siteMemberships ?? null
27961
- }
27962
- })
29751
+ buildRender: (source) => {
29752
+ const events = source.events == null ? null : scopeEventRegistrationSourceEvents(source);
29753
+ return {
29754
+ display: buildEventRegistrationDisplay(source),
29755
+ hydration: {
29756
+ occurrenceContext: source.occurrenceContext ?? null,
29757
+ subject: source.subject ?? null,
29758
+ events,
29759
+ wholeSeriesOfferings: source.wholeSeriesOfferings ?? null,
29760
+ wholeSeriesOffering: resolveWholeSeriesOffering(source),
29761
+ maxTickets: source.maxTickets ?? void 0,
29762
+ showVenue: source.showVenue,
29763
+ showCapacity: source.showCapacity,
29764
+ successMessage: source.successMessage,
29765
+ buttonVariant: source.buttonVariant,
29766
+ overlayEnabled: source.overlayEnabled,
29767
+ spamProtectionEnabled: source.spamProtectionEnabled,
29768
+ supportEmail: source.supportEmail,
29769
+ sitePasses: source.sitePasses ?? null,
29770
+ siteMemberships: source.siteMemberships ?? null
29771
+ }
29772
+ };
29773
+ }
27963
29774
  });
27964
29775
  function normalizeEventRegistrationEventInput(value) {
27965
- if (!isRecord$5(value)) return value;
29776
+ if (!isRecord$7(value)) return value;
27966
29777
  return {
27967
29778
  ...value,
27968
29779
  destination: resolveRuntimeEventDestination(value),
27969
29780
  pricing: resolveRuntimeEventPricing(value)
27970
29781
  };
27971
29782
  }
27972
- function isRecord$5(value) {
29783
+ function isRecord$7(value) {
27973
29784
  return typeof value === "object" && value !== null && !Array.isArray(value);
27974
29785
  }
27975
29786
  const eventRegistrationBlockDefinition = {
@@ -28743,7 +30554,8 @@ const eventCollectionSourceSchemaShape = {
28743
30554
  buttonVariant: eventButtonVariantSchema.optional(),
28744
30555
  buttonText: z.string().optional(),
28745
30556
  emptyMessage: z.string().optional(),
28746
- className: z.string().optional()
30557
+ className: z.string().optional(),
30558
+ bookingTimeZone: z.string().optional()
28747
30559
  };
28748
30560
  const eventCollectionDisplaySchemaShape = {
28749
30561
  events: z.array(publicEventSchema),
@@ -28778,7 +30590,15 @@ const eventCollectionHydrationSchema = z.object({
28778
30590
  prefetchedEvents: z.boolean(),
28779
30591
  eventSurface: eventSurfaceScopeSchema$1,
28780
30592
  filterEventPresets: z.string().nullable(),
28781
- scheduleScope: eventScheduleScopeSchema$1
30593
+ scheduleScope: eventScheduleScopeSchema$1,
30594
+ calendarContext: z.object({
30595
+ timeZone: z.string().transform(toBookingTimeZone),
30596
+ initialToday: z.object({
30597
+ year: z.number().int(),
30598
+ month: z.number().int().min(1).max(12),
30599
+ day: z.number().int().min(1).max(31)
30600
+ })
30601
+ })
28782
30602
  });
28783
30603
  function buildEventCollectionDisplay(source) {
28784
30604
  return {
@@ -28823,14 +30643,19 @@ function buildEventCollectionPresentationDisplay(source) {
28823
30643
  emptyMessage: source.emptyMessage ?? "No upcoming events scheduled."
28824
30644
  };
28825
30645
  }
28826
- function buildEventCollectionHydration(source) {
30646
+ function buildEventCollectionHydration(source, now = /* @__PURE__ */ new Date()) {
30647
+ const timeZone = toBookingTimeZone(source.bookingTimeZone);
28827
30648
  return {
28828
30649
  siteId: source.siteId,
28829
30650
  stage: source.stage,
28830
30651
  prefetchedEvents: source.events != null,
28831
30652
  eventSurface: normalizeEventSurfaceScope(source.eventSurface),
28832
30653
  filterEventPresets: normalizeEventSurfacePresetsCsv(source.filterEventPresets),
28833
- scheduleScope: normalizeEventScheduleScope(source.scheduleScope)
30654
+ scheduleScope: normalizeEventScheduleScope(source.scheduleScope),
30655
+ calendarContext: {
30656
+ timeZone,
30657
+ initialToday: getLocalScheduleDate(now, timeZone)
30658
+ }
28834
30659
  };
28835
30660
  }
28836
30661
  //#endregion
@@ -30821,7 +32646,7 @@ function inferLegacyOfferingsPreviewGroupOrder(overrides) {
30821
32646
  return keys.length >= 2 ? keys : [];
30822
32647
  }
30823
32648
  function normalizeOfferingsPreviewContentForWrite(value) {
30824
- if (!isRecord$4(value)) return null;
32649
+ if (!isRecord$6(value)) return null;
30825
32650
  const groupOrderValue = value.groupOrder;
30826
32651
  if (Array.isArray(groupOrderValue)) {
30827
32652
  const normalized = normalizeOfferingsPreviewGroupOrder(groupOrderValue);
@@ -30834,7 +32659,7 @@ function normalizeOfferingsPreviewContentForWrite(value) {
30834
32659
  ...value,
30835
32660
  groupOrder: []
30836
32661
  };
30837
- const legacyOrder = inferLegacyOfferingsPreviewGroupOrder(Array.isArray(value.groupOverrides) ? value.groupOverrides.filter(isRecord$4) : []);
32662
+ const legacyOrder = inferLegacyOfferingsPreviewGroupOrder(Array.isArray(value.groupOverrides) ? value.groupOverrides.filter(isRecord$6) : []);
30838
32663
  return legacyOrder.length > 0 ? {
30839
32664
  ...value,
30840
32665
  groupOrder: legacyOrder
@@ -30848,7 +32673,7 @@ function uniqueOfferingsPreviewSourceKeys(values) {
30848
32673
  }
30849
32674
  return keys;
30850
32675
  }
30851
- function isRecord$4(value) {
32676
+ function isRecord$6(value) {
30852
32677
  return typeof value === "object" && value !== null && !Array.isArray(value);
30853
32678
  }
30854
32679
  //#endregion
@@ -31226,7 +33051,7 @@ function isOfferingsPreviewItemLimit(value) {
31226
33051
  return value === "1" || value === "2" || value === "3" || value === "4" || value === "5" || value === "6" || value === null;
31227
33052
  }
31228
33053
  function normalizeOfferingsPreviewGroupOverride(value) {
31229
- if (!isRecord$3(value)) return [];
33054
+ if (!isRecord$5(value)) return [];
31230
33055
  const override = {};
31231
33056
  if (isOfferingsPreviewSourceKey(value.sourceKey) || value.sourceKey === null) override.sourceKey = value.sourceKey;
31232
33057
  if (typeof value.title === "string" || value.title === null) override.title = value.title;
@@ -31249,7 +33074,7 @@ function normalizeSectionStylesContent(value) {
31249
33074
  }
31250
33075
  function normalizeBoxStylesContent(value) {
31251
33076
  if (value === null) return null;
31252
- if (!isRecord$3(value)) return void 0;
33077
+ if (!isRecord$5(value)) return void 0;
31253
33078
  const { background: _background, ...rest } = value;
31254
33079
  const background = parseBackgroundContent(value.background);
31255
33080
  return {
@@ -31257,7 +33082,7 @@ function normalizeBoxStylesContent(value) {
31257
33082
  ...background ? { background } : {}
31258
33083
  };
31259
33084
  }
31260
- function isRecord$3(value) {
33085
+ function isRecord$5(value) {
31261
33086
  return typeof value === "object" && value !== null && !Array.isArray(value);
31262
33087
  }
31263
33088
  const embedManifest = createBlockManifest({
@@ -34478,6 +36303,23 @@ function deriveNewsletterSignupRuntimeSource(values) {
34478
36303
  tags: parseTrimmedString(normalized.tags)
34479
36304
  };
34480
36305
  }
36306
+ function sanitizeNewsletterSignupPublicContent(content) {
36307
+ if (!isRecord$4(content)) return content;
36308
+ const provider = parseNewsletterSignupDestinationProvider(content.destinationProvider) ?? getProviderFromExplicitDestination(content.destination);
36309
+ const { destination: _destination, destinationProvider: _destinationProvider, mailchimpAudienceId: _mailchimpAudienceId, mailchimpTags: _mailchimpTags, listId, tags, ...publicContent } = content;
36310
+ const mutablePublicContent = { ...publicContent };
36311
+ if (provider !== "mailchimp") {
36312
+ if ("listId" in content) mutablePublicContent.listId = listId;
36313
+ if ("tags" in content) mutablePublicContent.tags = tags;
36314
+ }
36315
+ return mutablePublicContent;
36316
+ }
36317
+ function getProviderFromExplicitDestination(destination) {
36318
+ if (!isRecord$4(destination) || typeof destination.tag !== "string") return null;
36319
+ if (destination.tag === "riverbank_list") return "riverbank";
36320
+ if (destination.tag === "mailchimp_audience") return "mailchimp";
36321
+ return null;
36322
+ }
34481
36323
  const BLOCK_ID_PATTERN = /^[A-Za-z0-9._:-]{1,200}$/;
34482
36324
  const SIGNED_DESTINATION_REF_PATTERN = /^[A-Za-z0-9._~-]{16,1000}$/;
34483
36325
  function parseTrimmedString(value) {
@@ -34485,6 +36327,9 @@ function parseTrimmedString(value) {
34485
36327
  const trimmed = value.trim();
34486
36328
  return trimmed.length > 0 ? trimmed : null;
34487
36329
  }
36330
+ function isRecord$4(value) {
36331
+ return typeof value === "object" && value !== null;
36332
+ }
34488
36333
  //#endregion
34489
36334
  //#region ../blocks/src/system/runtime/nodes/newsletter-form.types.ts
34490
36335
  /**
@@ -34720,7 +36565,7 @@ function projectNewsletterFormHydrationTarget(source) {
34720
36565
  tag: "legacy-direct-list",
34721
36566
  listId: source.listId
34722
36567
  };
34723
- default: return assertNever$2(source);
36568
+ default: return assertNever$3(source);
34724
36569
  }
34725
36570
  }
34726
36571
  function parseRequiredNewsletterSignupBlockId(value) {
@@ -36898,7 +38743,7 @@ function isNonProductionEnv() {
36898
38743
  //#region ../blocks/src/system/data/linkedContentEntries.ts
36899
38744
  const LINKED_CONTENT_ENTRY_META_KEY = "__rbLinkedContentEntry";
36900
38745
  function coerceLinkedContentEntriesPayload(payload) {
36901
- if (!isRecord$2(payload)) return payload;
38746
+ if (!isRecord$3(payload)) return payload;
36902
38747
  const response = payload;
36903
38748
  if (!Array.isArray(response.entries)) return payload;
36904
38749
  const contentType = parsePublicContentTypeMeta(response.contentType);
@@ -36906,12 +38751,12 @@ function coerceLinkedContentEntriesPayload(payload) {
36906
38751
  return response.entries.map((entry) => enrichLinkedContentEntry(entry, contentType));
36907
38752
  }
36908
38753
  function enrichLinkedContentEntry(entry, contentType) {
36909
- if (!isRecord$2(entry)) return entry;
38754
+ if (!isRecord$3(entry)) return entry;
36910
38755
  const entryId = typeof entry.id === "string" ? entry.id : null;
36911
38756
  if (!entryId) return entry;
36912
38757
  const title = typeof entry.title === "string" && entry.title.trim().length > 0 ? entry.title : entryId;
36913
38758
  const optimisticHash = typeof entry.optimisticHash === "string" && entry.optimisticHash.length > 0 ? entry.optimisticHash : null;
36914
- const content = isRecord$2(entry.content) ? entry.content : {};
38759
+ const content = isRecord$3(entry.content) ? entry.content : {};
36915
38760
  const meta = {
36916
38761
  kind: "linked-content-entry",
36917
38762
  entryId,
@@ -36994,7 +38839,7 @@ function resolveLinkedContentEditSupport(contentType) {
36994
38839
  kind: "unsupported-domain-backed",
36995
38840
  reason: metadata.reason
36996
38841
  };
36997
- default: return assertNever$2(metadata);
38842
+ default: return assertNever$3(metadata);
36998
38843
  }
36999
38844
  }
37000
38845
  function contentTypeKeyToLabel(key) {
@@ -37004,7 +38849,7 @@ function capitalize(value) {
37004
38849
  return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
37005
38850
  }
37006
38851
  function parsePublicContentTypeMeta(value) {
37007
- if (!isRecord$2(value)) return null;
38852
+ if (!isRecord$3(value)) return null;
37008
38853
  if (typeof value.key !== "string" || value.key.trim().length === 0) return null;
37009
38854
  return {
37010
38855
  key: value.key,
@@ -37014,7 +38859,7 @@ function parsePublicContentTypeMeta(value) {
37014
38859
  };
37015
38860
  }
37016
38861
  function parsePublicContentTypeLinkedEntryEditing(value) {
37017
- if (!isRecord$2(value) || typeof value.kind !== "string") return null;
38862
+ if (!isRecord$3(value) || typeof value.kind !== "string") return null;
37018
38863
  switch (value.kind) {
37019
38864
  case "generic": return { kind: "generic" };
37020
38865
  case "unsupported-domain-backed": return {
@@ -37025,7 +38870,7 @@ function parsePublicContentTypeLinkedEntryEditing(value) {
37025
38870
  }
37026
38871
  }
37027
38872
  function parsePublicContentTypeFieldMeta(value) {
37028
- if (!isRecord$2(value)) return [];
38873
+ if (!isRecord$3(value)) return [];
37029
38874
  if (typeof value.id !== "string" || typeof value.type !== "string") return [];
37030
38875
  return [{
37031
38876
  id: value.id,
@@ -37039,7 +38884,7 @@ function parseStringArray(value) {
37039
38884
  if (!Array.isArray(value)) return null;
37040
38885
  return value.filter((item) => typeof item === "string");
37041
38886
  }
37042
- function isRecord$2(value) {
38887
+ function isRecord$3(value) {
37043
38888
  return typeof value === "object" && value !== null && !Array.isArray(value);
37044
38889
  }
37045
38890
  //#endregion
@@ -37060,10 +38905,10 @@ function normalizeDataLoaders(def) {
37060
38905
  */
37061
38906
  function normalizeContent(block) {
37062
38907
  const source = block.draftContent ?? block.content ?? block.publishedContent ?? {};
37063
- if (!isObjectRecord(source)) return {};
38908
+ if (!isObjectRecord$1(source)) return {};
37064
38909
  if ("data" in source) {
37065
38910
  const data = source.data;
37066
- if (isObjectRecord(data)) return data;
38911
+ if (isObjectRecord$1(data)) return data;
37067
38912
  if (data === null || data === void 0) return {};
37068
38913
  }
37069
38914
  return source;
@@ -37246,7 +39091,7 @@ const SUPPORTED_LOADER_ENDPOINTS$1 = [
37246
39091
  //#endregion
37247
39092
  //#region ../blocks/src/system/data/normalizeLoaderParams.ts
37248
39093
  function toStringParams(value) {
37249
- if (!isObjectRecord(value)) return {};
39094
+ if (!isObjectRecord$1(value)) return {};
37250
39095
  const out = {};
37251
39096
  for (const [k, v] of Object.entries(value)) if (typeof v === "string") out[k] = v;
37252
39097
  return out;
@@ -37844,7 +39689,7 @@ function describeSiteVerticalConfigIssue(issue) {
37844
39689
  case "duplicate_preset_id":
37845
39690
  case "duplicate_canonical_behaviour":
37846
39691
  case "future_preset_selected": return issue.code;
37847
- default: return assertNever$2(issue);
39692
+ default: return assertNever$3(issue);
37848
39693
  }
37849
39694
  }
37850
39695
  const VerticalIdSchema = z.string().trim().transform((value, ctx) => {
@@ -37885,7 +39730,7 @@ z.object({
37885
39730
  });
37886
39731
  //#endregion
37887
39732
  //#region ../api/src/utils/isRecord.ts
37888
- function isRecord$1(value) {
39733
+ function isRecord$2(value) {
37889
39734
  return typeof value === "object" && value !== null && !Array.isArray(value);
37890
39735
  }
37891
39736
  //#endregion
@@ -38134,7 +39979,7 @@ function sanitizeMarks(marks) {
38134
39979
  if (!Array.isArray(marks) || marks.length === 0) return;
38135
39980
  const sanitized = marks.flatMap((mark) => {
38136
39981
  if (mark.type === "bold" || mark.type === "strong" || mark.type === "italic" || mark.type === "em") return [{ type: mark.type }];
38137
- if (mark.type === "link" && isRecord$1(mark.attrs) && typeof mark.attrs.href === "string") {
39982
+ if (mark.type === "link" && isRecord$2(mark.attrs) && typeof mark.attrs.href === "string") {
38138
39983
  const attrs = { href: mark.attrs.href };
38139
39984
  if (typeof mark.attrs.target === "string" && mark.attrs.target.trim().length > 0) attrs.target = mark.attrs.target;
38140
39985
  if (typeof mark.attrs.rel === "string" && mark.attrs.rel.trim().length > 0) attrs.rel = mark.attrs.rel;
@@ -38329,7 +40174,7 @@ const ENTRY_REF_KEYS = [
38329
40174
  "entryId",
38330
40175
  "slug"
38331
40176
  ];
38332
- function isRecord(value) {
40177
+ function isRecord$1(value) {
38333
40178
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
38334
40179
  }
38335
40180
  function extractManualEntryRef(item) {
@@ -38337,7 +40182,7 @@ function extractManualEntryRef(item) {
38337
40182
  const trimmed = item.trim();
38338
40183
  return trimmed.length > 0 ? trimmed : null;
38339
40184
  }
38340
- if (!isRecord(item)) return null;
40185
+ if (!isRecord$1(item)) return null;
38341
40186
  for (const key of ENTRY_REF_KEYS) {
38342
40187
  const raw = item[key];
38343
40188
  if (typeof raw === "string" && raw.trim().length > 0) return raw.trim();
@@ -38642,6 +40487,126 @@ function requireSdkTheme(theme, siteId, reason = "cms_theme_absent") {
38642
40487
  return theme;
38643
40488
  }
38644
40489
  //#endregion
40490
+ //#region src/rendering/helpers/siteCustomBlocks.ts
40491
+ /**
40492
+ * Prefer the DB-backed custom block manifest list (`site.customBlocks`) when available,
40493
+ * but fall back to SDK config custom blocks (`site.sdkConfig.customBlocks`) for SDK-mode sites.
40494
+ *
40495
+ * This avoids subtle DX bugs where one source is populated but the renderer/prefetch layer
40496
+ * is looking at the other.
40497
+ */
40498
+ function resolveSiteCustomBlocks(site) {
40499
+ const dbBlocks = site.customBlocks;
40500
+ if (Array.isArray(dbBlocks) && dbBlocks.length > 0) return dbBlocks;
40501
+ const sdkBlocks = site.sdkConfig?.customBlocks;
40502
+ if (Array.isArray(sdkBlocks) && sdkBlocks.length > 0) return sdkBlocks;
40503
+ }
40504
+ //#endregion
40505
+ //#region src/rendering/helpers/pageRuntimeSupport.ts
40506
+ function convertPageResponseBlock(block) {
40507
+ if (!isRecord(block)) throw new Error("Invalid block format in page API response");
40508
+ if (typeof block.id !== "string" && block.id !== null) throw new Error(`Invalid block id in page: expected string or null, got ${typeof block.id}`);
40509
+ if (typeof block.kind !== "string") throw new Error(`Invalid block kind in page: expected string, got ${typeof block.kind}`);
40510
+ if (typeof block.purpose !== "string") throw new Error(`Invalid block purpose in page: expected string, got ${typeof block.purpose}`);
40511
+ const admission = readInvalidBlockAdmission(block.admission);
40512
+ return {
40513
+ id: block.id,
40514
+ kind: block.kind,
40515
+ purpose: block.purpose,
40516
+ content: sanitizeSdkRuntimeBlockContent(block.kind, readContentRecord(block.content) ?? {}),
40517
+ draftContent: sanitizeNullableSdkRuntimeBlockContent(block.kind, readNestedContentRecord(block.draftContent)),
40518
+ ...admission ? { admission } : {}
40519
+ };
40520
+ }
40521
+ function readInvalidBlockAdmission(value) {
40522
+ if (!isRecord(value) || value.kind !== "invalid" || !isRecord(value.error)) return null;
40523
+ if (value.error.kind !== "invalid_manifest_content" && value.error.kind !== "manifest_unavailable") return null;
40524
+ return value;
40525
+ }
40526
+ function buildPageOutline(input) {
40527
+ return {
40528
+ name: input.name,
40529
+ path: input.path ?? input.fallbackPath,
40530
+ purpose: input.purpose,
40531
+ blocks: input.blocks.map(convertPageResponseBlock)
40532
+ };
40533
+ }
40534
+ function buildRenderablePageOutline(input) {
40535
+ const page = buildPageOutline(input);
40536
+ return {
40537
+ ...page,
40538
+ blocks: page.blocks.map((block) => admitSdkRuntimePageBlock(block, input.customBlocks))
40539
+ };
40540
+ }
40541
+ function projectRenderablePageOutline(page) {
40542
+ return {
40543
+ ...page,
40544
+ blocks: page.blocks.flatMap((block) => block.admission.kind === "renderable" ? [{
40545
+ id: block.id,
40546
+ kind: block.kind,
40547
+ purpose: block.purpose,
40548
+ content: block.admission.content,
40549
+ ...block.bindings ? { bindings: block.bindings } : {}
40550
+ }] : [])
40551
+ };
40552
+ }
40553
+ function admitSdkRuntimePageBlock(block, customBlocks) {
40554
+ const base = {
40555
+ id: block.id,
40556
+ kind: block.kind,
40557
+ purpose: block.purpose,
40558
+ ...block.bindings ? { bindings: block.bindings } : {}
40559
+ };
40560
+ const transportedAdmission = readInvalidBlockAdmission(block.admission);
40561
+ if (transportedAdmission) return invalidRenderableBlock(base, transportedAdmission.error);
40562
+ const manifest = resolveSdkBlockManifest(block.kind, customBlocks);
40563
+ if (!manifest) return invalidRenderableBlock(base, { kind: "manifest_unavailable" });
40564
+ const content = block.content ?? {};
40565
+ const renderable = admitTransportedRenderableManifestContent(compileManifestContentContract(manifest), content);
40566
+ if (!renderable.ok) return invalidRenderableBlock(base, {
40567
+ kind: "invalid_manifest_content",
40568
+ error: renderable.error
40569
+ });
40570
+ return {
40571
+ ...base,
40572
+ admission: {
40573
+ kind: "renderable",
40574
+ content: renderable.value
40575
+ }
40576
+ };
40577
+ }
40578
+ function resolveSdkBlockManifest(blockKind, customBlocks) {
40579
+ const customBlock = customBlocks?.find((candidate) => candidate.id === blockKind);
40580
+ return customBlock ? parseFieldDefinitions(customBlock.fields) : getBlockDefinition(blockKind)?.manifest ?? null;
40581
+ }
40582
+ function invalidRenderableBlock(block, error) {
40583
+ return {
40584
+ ...block,
40585
+ admission: {
40586
+ kind: "invalid",
40587
+ error
40588
+ }
40589
+ };
40590
+ }
40591
+ function isRecord(value) {
40592
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
40593
+ }
40594
+ function readContentRecord(value) {
40595
+ return isRecord(value) ? value : void 0;
40596
+ }
40597
+ function readNestedContentRecord(value) {
40598
+ if (!isRecord(value)) return null;
40599
+ return readContentRecord(value.data) ?? null;
40600
+ }
40601
+ function sanitizeSdkRuntimeBlockContent(blockKind, content) {
40602
+ if (blockKind !== "block.newsletter-signup") return content;
40603
+ const sanitized = sanitizeNewsletterSignupPublicContent(content);
40604
+ return isRecord(sanitized) ? sanitized : {};
40605
+ }
40606
+ function sanitizeNullableSdkRuntimeBlockContent(blockKind, content) {
40607
+ return content ? sanitizeSdkRuntimeBlockContent(blockKind, content) : null;
40608
+ }
40609
+ //#endregion
38645
40610
  //#region src/rendering/hooks/usePage.ts
38646
40611
  /**
38647
40612
  * Client-side React hook to fetch page data.
@@ -38741,24 +40706,15 @@ function usePage(params) {
38741
40706
  const { page: pageData } = pageResponse;
38742
40707
  const canonicalPagePath = pageData.path ?? path;
38743
40708
  const mediaAssets = Array.isArray(pageResponse.mediaAssets) && pageResponse.mediaAssets.length > 0 ? toMediaAssetSidecar(pageResponse.mediaAssets) : void 0;
38744
- const blocks = pageData.blocks.map((block) => {
38745
- if (!block || typeof block !== "object") throw new Error("Invalid block format in API response");
38746
- if (typeof block.id !== "string" && block.id !== null) throw new Error(`Invalid block id: expected string or null, got ${typeof block.id}`);
38747
- if (typeof block.kind !== "string") throw new Error(`Invalid block kind: expected string, got ${typeof block.kind}`);
38748
- if (typeof block.purpose !== "string") throw new Error(`Invalid block purpose: expected string, got ${typeof block.purpose}`);
38749
- return {
38750
- id: block.id,
38751
- kind: block.kind,
38752
- purpose: block.purpose
38753
- };
38754
- });
38755
- const pageOutline = {
40709
+ const pageOutline = buildRenderablePageOutline({
38756
40710
  name: pageData.name,
38757
40711
  path: canonicalPagePath,
40712
+ fallbackPath: path,
38758
40713
  purpose: pageData.purpose,
38759
- blocks
38760
- };
38761
- const resolvedData = await prefetchBlockData(pageOutline, {
40714
+ blocks: pageData.blocks,
40715
+ customBlocks: resolveSiteCustomBlocks(site)
40716
+ });
40717
+ const resolvedData = await prefetchBlockData(projectRenderablePageOutline(pageOutline), {
38762
40718
  siteId,
38763
40719
  pageId: pageId ?? pageData.id,
38764
40720
  previewStage: preview ? "preview" : "published"