riverbankcms 0.28.0 → 0.29.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.
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { A as evaluateSiteCommandExposure, E as parseAndPlanCommandBatchForTypes, F as entryIdentifierForCommand, It as themeSchema, K as bookingQuestionAttachmentSchema, Mr as asPageFlowPresetId, Nr as asSiteStyleId, T as createDryRunReport, V as eventBookingOfferIntentSchema, X as createCommandRef, Xr as parseSurfaceTree, Z as createContentEntryRef, Zn as getDirectImageUrlForSiteScopedPath, _n as pageDesignIntentSchemaVersion, _r as normalizeAmbiguousStoragePathForSite, br as MAX_MANAGEMENT_MEDIA_UPLOAD_BYTES, cn as asHeaderLayoutId, ct as require_dist, dn as headerStyleIdValues, dr as slugifyMediaIdentifier, fn as buttonStyleSelectionSchema, gn as pageDesignBoundaryTreatmentSchema, gt as headerPreferencesSchema, hr as getMimeType, it as createSiteCommandSiteRef, j as getSiteCommandMetadata, k as evaluateSiteCommandBatchExposure, kr as asAppearancePresetId, ln as asHeaderStyleId, m as isInternalResolvedLinkValue, mr as getExtensionForMimeType, n as sdkCourseBookingSettingsSchema, on as asFooterLookId, r as sdkEventOccurrenceInputSchema, s as RESOLVED_INTERNAL_LINK_ENTITY_TYPES, sn as footerLookIdValues, sr as buildMediaIdentifierFromFilename, st as slugPart, un as headerLayoutIdValues, ur as resolveMediaIdentifier, vn as footerPreferencesSchema, w as stableStringify, x as resolvePageUpsertSeo, y as eventVisibilitySchema, yr as FILE_SIZE_LIMITS } from "./sdk-event-upsert-BrJvTQ-D.mjs";
3
- import { C as createSecret, Cn as parsePageId, Et as resolveBookingTimeZoneId, L as parseFooterCompositionV1, Ln as unsafeAsEventOfferingId, On as unsafeAsAppointmentResourceId, Qn as unsafeAsStaffMemberId, Rn as unsafeAsEventSeriesId, St as parseBookingLocalDate, T as parseManagementKey, Tn as parseSiteId, Vn as unsafeAsOccurrenceId, Z as locationValueSchema, _ as publicReadFreshnessSeconds, c as parseVerticalId, d as DEFAULT_EVENT_TYPE_PRESETS, f as EVENT_PRESET_VALUES, g as getPublicReadCachePolicy, ir as ok, l as parseVerticalPresetId, o as areConfiguredBookingVerticalConfigsEqual, ot as formatDate, pn as assertNever, r as resolveEventCatalogue, rr as err, s as canonicalizeConfiguredBookingVerticalConfig, t as committedWriteProofSchema, u as resolveSiteVerticalBookingConfig, w as exposeSecret } from "./src-D9-RAM-l.mjs";
4
- import { n as CliSdkPeerError, t as loadConfigFile } from "./load-config-9DBnYiBm.mjs";
2
+ import { $n as slugifyMediaIdentifier, $t as footerLookIdValues, A as evaluateSiteCommandExposure, Bt as BREAKPOINT_XL, E as parseAndPlanCommandBatchForTypes, F as entryIdentifierForCommand, In as parseAspectRatioValue, It as themeSchema, K as bookingQuestionAttachmentSchema, Lr as parseSurfaceTree, Lt as BREAKPOINT_LG, Qn as resolveMediaIdentifier, Qt as asFooterLookId, Rt as BREAKPOINT_MD, T as createDryRunReport, V as eventBookingOfferIntentSchema, X as createCommandRef, Yn as buildMediaIdentifierFromFilename, Z as createContentEntryRef, _r as asAppearancePresetId, an as pageDesignBoundaryTreatmentSchema, ar as normalizeAmbiguousStoragePathForSite, br as asPageFlowPresetId, cr as MAX_MANAGEMENT_MEDIA_UPLOAD_BYTES, ct as require_dist, en as asHeaderLayoutId, gr as arrangementModes, in as buttonStyleSelectionSchema, it as createSiteCommandSiteRef, j as getSiteCommandMetadata, k as evaluateSiteCommandBatchExposure, m as isInternalResolvedLinkValue, n as sdkCourseBookingSettingsSchema, nn as headerLayoutIdValues, nr as getExtensionForMimeType, on as pageDesignIntentSchemaVersion, r as sdkEventOccurrenceInputSchema, rn as headerStyleIdValues, rr as getMimeType, s as RESOLVED_INTERNAL_LINK_ENTITY_TYPES, sn as footerPreferencesSchema, sr as FILE_SIZE_LIMITS, st as slugPart, tn as asHeaderStyleId, w as stableStringify, x as resolvePageUpsertSeo, xr as asSiteStyleId, xt as headerPreferencesSchema, y as eventVisibilitySchema, zn as getDirectImageUrlForSiteScopedPath, zt as BREAKPOINT_SM } from "./sdk-event-upsert-CXc13KPT.mjs";
3
+ import { $ as locationValueSchema, D as parseManagementKey, E as exposeSecret, J as assertAllowedPublicUrl, Kn as parsePageId, Ot as resolveBookingTimeZoneId, R as parseMailingListProviderScope, T as createSecret, Tt as parseBookingLocalDate, Yn as parseSiteId, a as resolveEventCatalogue, bn as unsafeAsAppointmentResourceId, c as areConfiguredBookingVerticalConfigsEqual, ct as formatDate, d as parseVerticalPresetId, f as resolveSiteVerticalBookingConfig, gn as assertNever, ir as unsafeAsOccurrenceId, l as canonicalizeConfiguredBookingVerticalConfig, m as EVENT_PRESET_VALUES, n as committedWriteProofSchema, nr as unsafeAsEventOfferingId, or as err, p as DEFAULT_EVENT_TYPE_PRESETS, rr as unsafeAsEventSeriesId, sr as ok, t as OFFLINE_PAYMENT_METHODS, u as parseVerticalId, v as getPublicReadCachePolicy, xn as unsafeAsStaffMemberId, y as publicReadFreshnessSeconds, z as parseFooterCompositionV1 } from "./src-4CBCWovR.mjs";
4
+ import { n as CliSdkPeerError, t as loadConfigFile } from "./load-config-JcidEUjc.mjs";
5
5
  import { Command } from "commander";
6
6
  import { AsyncLocalStorage } from "node:async_hooks";
7
7
  import * as fs$1 from "fs/promises";
8
8
  import * as path$1 from "path";
9
9
  import * as readline from "readline";
10
- import { z } from "zod";
10
+ import { safeParse, z } from "zod";
11
11
  import { constants } from "node:fs";
12
12
  import { basename, dirname as dirname$1, join } from "node:path";
13
13
  import { riverbankSiteConfigSchema, validateFieldIdConflicts } from "@riverbankcms/sdk/config/validation";
@@ -82,20 +82,20 @@ function isApiError(result) {
82
82
  function isApiSuccess(result) {
83
83
  return result.success === true;
84
84
  }
85
- function isRecord$10(value) {
85
+ function isRecord$11(value) {
86
86
  return typeof value === "object" && value !== null && !Array.isArray(value);
87
87
  }
88
88
  function isFieldError$1(value) {
89
- return isRecord$10(value) && typeof value.field === "string" && typeof value.code === "string" && typeof value.message === "string" && (value.capacityOverride === undefined || isCapacityOverrideRefusal(value.capacityOverride));
89
+ return isRecord$11(value) && typeof value.field === "string" && typeof value.code === "string" && typeof value.message === "string" && (value.capacityOverride === undefined || isCapacityOverrideRefusal(value.capacityOverride));
90
90
  }
91
91
  function fieldErrorsFromUnknown(value) {
92
92
  if (!Array.isArray(value)) return null;
93
93
  return value.every(isFieldError$1) ? value : null;
94
94
  }
95
95
  function extractEnvelopeErrorObject(body) {
96
- if (!isRecord$10(body)) return null;
96
+ if (!isRecord$11(body)) return null;
97
97
  const error = body.error;
98
- if (!isRecord$10(error)) return null;
98
+ if (!isRecord$11(error)) return null;
99
99
  if ("success" in body && body.success !== false) {
100
100
  return null;
101
101
  }
@@ -306,7 +306,7 @@ function userFacingZodErrorMap(issue) {
306
306
  return getUserFacingValidationMessage(issue);
307
307
  }
308
308
  function safeParseUserInput(schema, input) {
309
- return schema.safeParse(input, { error: userFacingZodErrorMap });
309
+ return safeParse(schema, input, { error: userFacingZodErrorMap });
310
310
  }
311
311
  function toFieldErrors(zodError, options = {}) {
312
312
  return zodError.issues.map((issue) => ({
@@ -339,6 +339,24 @@ function toFlattenedFieldErrors(zodError, options) {
339
339
  //#region ../api/src/retentionPolicies.ts
340
340
  /** Pure policy catalogue shared by retention execution and its API projections. */
341
341
  const retentionPolicyRegistry = {
342
+ assistant_messages: {
343
+ dataClass: "assistant_messages",
344
+ policyKind: "platform_auto_delete",
345
+ trigger: "created_at",
346
+ window: {
347
+ kind: "relative",
348
+ unit: "months",
349
+ value: 12
350
+ },
351
+ action: "hard_delete",
352
+ automation: {
353
+ kind: "scheduled",
354
+ jobKind: "assistant_conversation_cleanup",
355
+ executionModes: ["dry_run", "scheduled"],
356
+ executionSubstrate: "vercel_cron"
357
+ },
358
+ rationale: "Each assistant message expires after twelve months, regardless of later conversation activity. Empty conversations and their choices are removed too."
359
+ },
342
360
  portal_sessions: {
343
361
  dataClass: "portal_sessions",
344
362
  policyKind: "platform_auto_delete",
@@ -810,7 +828,9 @@ const PREVIEW_BLOCKED_REASONS = [
810
828
  "duplicate",
811
829
  "run_closed",
812
830
  "past_date",
813
- "invalid_schedule"
831
+ "invalid_schedule",
832
+ "stale",
833
+ "invalid_state"
814
834
  ];
815
835
 
816
836
  //#endregion
@@ -859,6 +879,7 @@ const pageCommitReasonSchema = z.enum([
859
879
  "invalid-document",
860
880
  "invalid-history",
861
881
  "path-unavailable",
882
+ "homepage-publication-required",
862
883
  "invalid-page-version",
863
884
  "invalid-block-reference",
864
885
  "invalid-content-reference",
@@ -905,6 +926,7 @@ const reasonMessages = {
905
926
  "invalid-revision": "The saved page version could not be read. Reload the page before continuing.",
906
927
  "invalid-document": "The saved page content could not be read. Reload the page before continuing.",
907
928
  "invalid-history": "The saved page history could not be read. Reload the page before continuing.",
929
+ "homepage-publication-required": "Publish this page first. Your current homepage is live, so the page that replaces it has to be published too.",
908
930
  "path-unavailable": "This address is already in use. Choose a different page address.",
909
931
  "invalid-page-version": "This saved page version could not be read. Choose another version.",
910
932
  "invalid-block-reference": "A saved block is missing from this page. Reload the page before continuing.",
@@ -934,6 +956,44 @@ function pageCommitReasonFromTag(tag) {
934
956
  //#endregion
935
957
  //#region ../api/src/pageJournalEndpoints.ts
936
958
  const PAGE_JOURNAL_ENDPOINT_DEFINITIONS = {
959
+ discardAssistantPage: {
960
+ path: "/sites/{siteId}/pages/{pageId}/assistant-discard",
961
+ method: "POST",
962
+ auth: "user",
963
+ responseKind: "json",
964
+ editLease: "unleased-editor",
965
+ errors: [
966
+ "validation:invalid_input",
967
+ "auth:forbidden",
968
+ "server:internal_error"
969
+ ]
970
+ },
971
+ constructAssistantPage: {
972
+ path: "/sites/{siteId}/pages/assistant",
973
+ method: "POST",
974
+ auth: "user",
975
+ responseKind: "json",
976
+ editLease: "unleased-editor",
977
+ errors: [
978
+ "validation:invalid_input",
979
+ "auth:forbidden",
980
+ "resource:conflict",
981
+ "server:internal_error"
982
+ ]
983
+ },
984
+ commitPageChangeBatch: {
985
+ path: "/sites/{siteId}/pages/{pageId}/journal",
986
+ method: "PUT",
987
+ auth: "user",
988
+ responseKind: "json",
989
+ editLease: "managed",
990
+ errors: [
991
+ "validation:invalid_input",
992
+ "resource:not_found",
993
+ "resource:conflict",
994
+ "server:internal_error"
995
+ ]
996
+ },
937
997
  getPagePublishReadinessFacts: {
938
998
  path: "/sites/{siteId}/pages/readiness-facts",
939
999
  method: "POST",
@@ -2836,48 +2896,6 @@ const NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2836
2896
  auth: "user",
2837
2897
  responseKind: "json"
2838
2898
  },
2839
- listNewsletterTemplates: {
2840
- path: "/sites/{siteId}/newsletter/templates",
2841
- method: "GET",
2842
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
2843
- auth: "user",
2844
- responseKind: "json"
2845
- },
2846
- createNewsletterTemplate: {
2847
- path: "/sites/{siteId}/newsletter/templates",
2848
- method: "POST",
2849
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
2850
- auth: "user",
2851
- responseKind: "json"
2852
- },
2853
- getNewsletterTemplate: {
2854
- path: "/sites/{siteId}/newsletter/templates/{templateId}",
2855
- method: "GET",
2856
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
2857
- auth: "user",
2858
- responseKind: "json"
2859
- },
2860
- updateNewsletterTemplate: {
2861
- path: "/sites/{siteId}/newsletter/templates/{templateId}",
2862
- method: "PATCH",
2863
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
2864
- auth: "user",
2865
- responseKind: "json"
2866
- },
2867
- deleteNewsletterTemplate: {
2868
- path: "/sites/{siteId}/newsletter/templates/{templateId}",
2869
- method: "DELETE",
2870
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
2871
- auth: "user",
2872
- responseKind: "json"
2873
- },
2874
- previewNewsletterTemplate: {
2875
- path: "/sites/{siteId}/newsletter/templates/{templateId}/preview",
2876
- method: "POST",
2877
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
2878
- auth: "user",
2879
- responseKind: "json"
2880
- },
2881
2899
  listNewsletterCampaigns: {
2882
2900
  path: "/sites/{siteId}/newsletter/campaigns",
2883
2901
  method: "GET",
@@ -2935,6 +2953,38 @@ const NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2935
2953
  responseKind: "json",
2936
2954
  demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN
2937
2955
  },
2956
+ listNewsletterWebsitePicker: {
2957
+ path: "/sites/{siteId}/newsletter/website-picker",
2958
+ method: "GET",
2959
+ auth: "user",
2960
+ responseKind: "json",
2961
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
2962
+ errors: []
2963
+ },
2964
+ renderNewsletterCampaignDraft: {
2965
+ path: "/sites/{siteId}/newsletter/campaigns/{campaignId}/render-draft",
2966
+ method: "POST",
2967
+ auth: "user",
2968
+ responseKind: "json",
2969
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
2970
+ errors: []
2971
+ },
2972
+ reviewNewsletterCampaign: {
2973
+ path: "/sites/{siteId}/newsletter/campaigns/{campaignId}/review",
2974
+ method: "POST",
2975
+ auth: "user",
2976
+ responseKind: "json",
2977
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
2978
+ errors: []
2979
+ },
2980
+ duplicateNewsletterCampaign: {
2981
+ path: "/sites/{siteId}/newsletter/campaigns/{campaignId}/duplicate",
2982
+ method: "POST",
2983
+ auth: "user",
2984
+ responseKind: "json",
2985
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
2986
+ errors: []
2987
+ },
2938
2988
  scheduleNewsletterCampaign: {
2939
2989
  path: "/sites/{siteId}/newsletter/campaigns/{campaignId}/schedule",
2940
2990
  method: "POST",
@@ -3499,25 +3549,6 @@ const EVENT_MANAGEMENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
3499
3549
  "server:internal_error"
3500
3550
  ]
3501
3551
  },
3502
- updateEventAddress: {
3503
- editLease: "managed",
3504
- path: "/sites/{siteId}/bookings/events/{eventId}/address",
3505
- method: "PATCH",
3506
- tags: [
3507
- "site-{siteId}",
3508
- "event-series-{siteId}",
3509
- TAG_TEMPLATES.siteRoutableContent,
3510
- TAG_TEMPLATES.siteRedirects
3511
- ],
3512
- auth: "user",
3513
- responseKind: "json",
3514
- errors: [
3515
- "validation:invalid_input",
3516
- "resource:not_found",
3517
- "resource:conflict",
3518
- "server:internal_error"
3519
- ]
3520
- },
3521
3552
  createWholeSeriesRun: {
3522
3553
  path: "/sites/{siteId}/bookings/events/{eventId}/runs",
3523
3554
  method: "POST",
@@ -4097,6 +4128,14 @@ const BILLING_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
4097
4128
  auth: "admin",
4098
4129
  responseKind: "json"
4099
4130
  },
4131
+ adminGrantTrial: {
4132
+ path: "/admin/billing/trial-grant",
4133
+ method: "POST",
4134
+ tags: ["admin", "billing"],
4135
+ auth: "admin",
4136
+ responseKind: "json",
4137
+ errors: []
4138
+ },
4100
4139
  changePlan: {
4101
4140
  path: "/billing/plan/change",
4102
4141
  method: "POST",
@@ -4484,6 +4523,54 @@ const PAGE_SURFACE_EDITOR_ENDPOINT_DEFINITIONS = {
4484
4523
  * person when nothing is attached to them.
4485
4524
  */
4486
4525
  const BOOKING_PEOPLE_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
4526
+ previewPersonRemoval: {
4527
+ path: "/sites/{siteId}/bookings/people/{personId}/removal-preview",
4528
+ method: "GET",
4529
+ auth: "user",
4530
+ responseKind: "json",
4531
+ tags: ["site-{siteId}", "customers-{siteId}"],
4532
+ errors: [
4533
+ "auth:forbidden",
4534
+ "resource:not_found",
4535
+ "server:internal_error"
4536
+ ]
4537
+ },
4538
+ removePerson: {
4539
+ path: "/sites/{siteId}/bookings/people/{personId}/removal",
4540
+ method: "POST",
4541
+ auth: "user",
4542
+ responseKind: "json",
4543
+ tags: ["site-{siteId}", "customers-{siteId}"],
4544
+ errors: [
4545
+ "auth:forbidden",
4546
+ "resource:not_found",
4547
+ "server:internal_error"
4548
+ ]
4549
+ },
4550
+ getPersonRemovalStatus: {
4551
+ path: "/sites/{siteId}/bookings/people/{personId}/removal",
4552
+ method: "GET",
4553
+ auth: "user",
4554
+ responseKind: "json",
4555
+ tags: ["site-{siteId}", "customers-{siteId}"],
4556
+ errors: [
4557
+ "auth:forbidden",
4558
+ "resource:not_found",
4559
+ "server:internal_error"
4560
+ ]
4561
+ },
4562
+ exportPersonData: {
4563
+ path: "/sites/{siteId}/bookings/people/{personId}/export.json",
4564
+ method: "GET",
4565
+ auth: "user",
4566
+ responseKind: "blob",
4567
+ tags: ["site-{siteId}", "customers-{siteId}"],
4568
+ errors: [
4569
+ "auth:forbidden",
4570
+ "resource:not_found",
4571
+ "server:internal_error"
4572
+ ]
4573
+ },
4487
4574
  getBookingPerson: {
4488
4575
  path: "/sites/{siteId}/bookings/people/{personId}",
4489
4576
  method: "GET",
@@ -4605,20 +4692,22 @@ const ENDPOINT_DEFINITIONS = {
4605
4692
  auth: "user",
4606
4693
  responseKind: "json"
4607
4694
  },
4608
- aiPlaygroundProposeMulti: {
4609
- path: "/sites/{siteId}/ai/playground/propose-multi",
4695
+ assistantConversation: {
4696
+ path: "/assistant/conversation",
4610
4697
  method: "POST",
4611
4698
  auth: "user",
4612
- responseKind: "stream"
4699
+ responseKind: "json",
4700
+ errors: []
4613
4701
  },
4614
- aiPlaygroundProposeMultiGet: {
4615
- path: "/sites/{siteId}/ai/playground/propose-multi",
4616
- method: "GET",
4702
+ assistantNewConversation: {
4703
+ path: "/assistant/conversation/new",
4704
+ method: "POST",
4617
4705
  auth: "user",
4618
- responseKind: "stream"
4706
+ responseKind: "json",
4707
+ errors: []
4619
4708
  },
4620
- assistantSuggestions: {
4621
- path: "/assistant/suggestions",
4709
+ assistantOutcome: {
4710
+ path: "/assistant/outcome",
4622
4711
  method: "POST",
4623
4712
  auth: "user",
4624
4713
  responseKind: "json",
@@ -4645,42 +4734,6 @@ const ENDPOINT_DEFINITIONS = {
4645
4734
  responseKind: "stream",
4646
4735
  errors: []
4647
4736
  },
4648
- aiPlaygroundPageConvert: {
4649
- path: "/sites/{siteId}/ai/playground/page-convert",
4650
- method: "POST",
4651
- auth: "user",
4652
- responseKind: "json"
4653
- },
4654
- aiPlaygroundPageConvertJobsCreate: {
4655
- path: "/sites/{siteId}/ai/playground/page-convert/jobs",
4656
- method: "POST",
4657
- auth: "user",
4658
- responseKind: "json"
4659
- },
4660
- aiPlaygroundPageConvertJobsList: {
4661
- path: "/sites/{siteId}/ai/playground/page-convert/jobs",
4662
- method: "GET",
4663
- auth: "user",
4664
- responseKind: "json"
4665
- },
4666
- aiPlaygroundPageConvertJobGet: {
4667
- path: "/sites/{siteId}/ai/playground/page-convert/jobs/{jobId}",
4668
- method: "GET",
4669
- auth: "user",
4670
- responseKind: "json"
4671
- },
4672
- aiPlaygroundPageConvertJobRun: {
4673
- path: "/sites/{siteId}/ai/playground/page-convert/jobs/{jobId}/run",
4674
- method: "POST",
4675
- auth: "user",
4676
- responseKind: "json"
4677
- },
4678
- aiPlaygroundPageConvertJobEvents: {
4679
- path: "/sites/{siteId}/ai/playground/page-convert/jobs/{jobId}/events",
4680
- method: "GET",
4681
- auth: "user",
4682
- responseKind: "json"
4683
- },
4684
4737
  aiGenerateSite: {
4685
4738
  path: "/sites/{siteId}/ai/generate",
4686
4739
  method: "POST",
@@ -5463,6 +5516,32 @@ const ENDPOINT_DEFINITIONS = {
5463
5516
  auth: "user",
5464
5517
  responseKind: "json"
5465
5518
  },
5519
+ getRouteAddress: {
5520
+ path: "/sites/{siteId}/content-address/{targetKind}/{targetId}",
5521
+ method: "GET",
5522
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteRoutableContent],
5523
+ auth: "user",
5524
+ responseKind: "json",
5525
+ errors: ["resource:not_found", "resource:conflict"]
5526
+ },
5527
+ changeRouteAddress: {
5528
+ editLease: "managed",
5529
+ path: "/sites/{siteId}/content-address/{targetKind}/{targetId}",
5530
+ method: "PATCH",
5531
+ tags: [
5532
+ TAG_TEMPLATES.site,
5533
+ TAG_TEMPLATES.siteRoutableContent,
5534
+ TAG_TEMPLATES.siteRedirects
5535
+ ],
5536
+ auth: "user",
5537
+ responseKind: "json",
5538
+ errors: [
5539
+ "validation:invalid_input",
5540
+ "resource:not_found",
5541
+ "resource:conflict",
5542
+ "server:internal_error"
5543
+ ]
5544
+ },
5466
5545
  updateRouteMetadata: {
5467
5546
  editLease: "managed",
5468
5547
  path: "/sites/{siteId}/routes/{routeId}/metadata",
@@ -5653,6 +5732,30 @@ const ENDPOINT_DEFINITIONS = {
5653
5732
  auth: "user",
5654
5733
  responseKind: "json"
5655
5734
  },
5735
+ previewWebsiteOperation: {
5736
+ path: "/sites/{siteId}/website/preview",
5737
+ method: "POST",
5738
+ auth: "user",
5739
+ responseKind: "json",
5740
+ errors: [
5741
+ "validation:invalid_input",
5742
+ "auth:insufficient_permissions",
5743
+ "server:internal_error"
5744
+ ]
5745
+ },
5746
+ confirmWebsiteOperation: {
5747
+ editLease: "unleased-editor",
5748
+ path: "/sites/{siteId}/website/confirm",
5749
+ method: "POST",
5750
+ auth: "user",
5751
+ responseKind: "json",
5752
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings],
5753
+ errors: [
5754
+ "validation:invalid_input",
5755
+ "auth:insufficient_permissions",
5756
+ "server:internal_error"
5757
+ ]
5758
+ },
5656
5759
  previewSiteBrandingOperation: {
5657
5760
  path: "/sites/{siteId}/settings/general/preview",
5658
5761
  method: "POST",
@@ -5870,6 +5973,20 @@ const ENDPOINT_DEFINITIONS = {
5870
5973
  errors: ["resource:not_found", "server:internal_error"],
5871
5974
  responseKind: "json"
5872
5975
  },
5976
+ updateFooterCredit: {
5977
+ path: "/sites/{siteId}/settings/footer-credit",
5978
+ method: "POST",
5979
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings],
5980
+ auth: "user",
5981
+ demoMutation: DEMO_MUTATION_ALLOWED,
5982
+ errors: [
5983
+ "auth:forbidden",
5984
+ "validation:invalid_input",
5985
+ "resource:not_found",
5986
+ "server:internal_error"
5987
+ ],
5988
+ responseKind: "json"
5989
+ },
5873
5990
  updateFooterComposition: {
5874
5991
  editLease: "unleased-editor",
5875
5992
  path: "/sites/{siteId}/settings/footer-composition",
@@ -6227,15 +6344,27 @@ const ENDPOINT_DEFINITIONS = {
6227
6344
  auth: "user",
6228
6345
  responseKind: "json"
6229
6346
  },
6230
- accountRevokeSessions: {
6231
- path: "/account/sessions/revoke",
6347
+ accountAnswerHeardFrom: {
6348
+ path: "/account/heard-from",
6232
6349
  method: "POST",
6233
6350
  tags: ["account"],
6234
6351
  auth: "user",
6352
+ errors: [
6353
+ "auth:unauthenticated",
6354
+ "validation:invalid_input",
6355
+ "server:internal_error"
6356
+ ],
6235
6357
  responseKind: "json"
6236
6358
  },
6237
- adminListInvites: {
6238
- path: "/admin/invites",
6359
+ accountRevokeSessions: {
6360
+ path: "/account/sessions/revoke",
6361
+ method: "POST",
6362
+ tags: ["account"],
6363
+ auth: "user",
6364
+ responseKind: "json"
6365
+ },
6366
+ adminListInvites: {
6367
+ path: "/admin/invites",
6239
6368
  method: "GET",
6240
6369
  tags: ["admin", "invites"],
6241
6370
  auth: "admin",
@@ -8799,14 +8928,6 @@ const ENDPOINT_DEFINITIONS = {
8799
8928
  auth: "user",
8800
8929
  responseKind: "json"
8801
8930
  },
8802
- aiPlaygroundApply: {
8803
- editLease: "unleased-editor",
8804
- path: "/sites/{siteId}/ai/playground/apply",
8805
- method: "POST",
8806
- tags: ["site-{siteId}"],
8807
- auth: "user",
8808
- responseKind: "json"
8809
- },
8810
8931
  billingStatus: {
8811
8932
  path: "/billing/status",
8812
8933
  method: "GET",
@@ -9759,6 +9880,7 @@ const ENDPOINT_DEFINITIONS = {
9759
9880
  responseKind: "json",
9760
9881
  errors: [
9761
9882
  "resource:not_found",
9883
+ "resource:conflict",
9762
9884
  "validation:invalid_input",
9763
9885
  "server:internal_error"
9764
9886
  ]
@@ -10250,17 +10372,6 @@ const blockLocationFields = {
10250
10372
  blockIdentifier: z.string().nullable(),
10251
10373
  stage: z.enum(["draft", "published"])
10252
10374
  };
10253
- const newsletterUsageLocationSchemas = [z.object({
10254
- kind: z.literal("newsletterTemplate"),
10255
- siteId: identifierSchema,
10256
- templateId: identifierSchema,
10257
- templateName: z.string()
10258
- }), z.object({
10259
- kind: z.literal("newsletterCampaign"),
10260
- siteId: identifierSchema,
10261
- campaignId: identifierSchema,
10262
- campaignName: z.string()
10263
- })];
10264
10375
  const mediaUsageLocationSchema = z.discriminatedUnion("kind", [
10265
10376
  z.object({
10266
10377
  kind: z.literal("appointmentService"),
@@ -10268,7 +10379,12 @@ const mediaUsageLocationSchema = z.discriminatedUnion("kind", [
10268
10379
  serviceId: identifierSchema,
10269
10380
  serviceTitle: z.string()
10270
10381
  }),
10271
- ...newsletterUsageLocationSchemas,
10382
+ z.object({
10383
+ kind: z.literal("newsletterCampaign"),
10384
+ siteId: identifierSchema,
10385
+ campaignId: identifierSchema,
10386
+ campaignName: z.string()
10387
+ }),
10272
10388
  z.object({
10273
10389
  kind: z.literal("pageBlock"),
10274
10390
  ...blockLocationFields,
@@ -10368,7 +10484,7 @@ const sdkMediaDeleteApplyResponseSchema = z.discriminatedUnion("kind", [
10368
10484
  *
10369
10485
  * Goal: a single source-of-truth for link shapes used across dashboard + SDK.
10370
10486
  * - Types live in `packages/api/src/navigation/types.ts`
10371
- * - Schemas live here and are tied to types via `satisfies z.ZodType<...>`
10487
+ * - Schemas live here and are tied to types via `satisfies z.core.$ZodType<...>`
10372
10488
  */
10373
10489
  const portalLinkPayloadSchema = z.object({
10374
10490
  kind: z.literal("portal"),
@@ -10607,18 +10723,7 @@ const sdkPullVenueSchema = z.object({
10607
10723
  description: z.string().nullable(),
10608
10724
  timezone: z.string().nullable(),
10609
10725
  capacity: z.number().int().nonnegative().nullable(),
10610
- address: z.object({
10611
- line1: z.string().optional(),
10612
- line2: z.string().optional(),
10613
- city: z.string().optional(),
10614
- region: z.string().optional(),
10615
- postcode: z.string().optional(),
10616
- country: z.string().optional()
10617
- }),
10618
- location: z.object({
10619
- lat: z.number(),
10620
- lng: z.number()
10621
- }).partial().nullable(),
10726
+ addressVisibility: z.enum(["everyone", "booked"]).optional(),
10622
10727
  locationValue: locationValueSchema.nullable().optional(),
10623
10728
  mapEmbed: z.string().nullable(),
10624
10729
  updatedAt: sdkTimestampSchema$2,
@@ -10673,7 +10778,6 @@ const sdkPullEventRunCommonSchema = z.object({
10673
10778
  capacity: z.number().int().nonnegative().nullable(),
10674
10779
  bookingOffer: eventBookingOfferIntentSchema,
10675
10780
  venueRef: z.string().nullable(),
10676
- formRef: z.string().nullable(),
10677
10781
  bookingQuestions: bookingQuestionAttachmentSchema.nullable().optional(),
10678
10782
  settings: sdkJsonObjectSchema$1,
10679
10783
  presentation: z.unknown().optional(),
@@ -10861,7 +10965,6 @@ const BOOKING_SETTINGS_SECTION_KEYS = {
10861
10965
  ],
10862
10966
  appointments: [
10863
10967
  "appointmentReminderOverrides",
10864
- "appointmentRescheduleHours",
10865
10968
  "appointmentDefaultMinLeadMinutes",
10866
10969
  "appointmentDefaultMaxBookingNoticeMinutes",
10867
10970
  "appointmentBookingPolicies"
@@ -10884,6 +10987,125 @@ const BOOKING_SETTINGS_EMAIL_SEND_KEYS = [
10884
10987
  "appointmentStaffReminder"
10885
10988
  ];
10886
10989
 
10990
+ //#endregion
10991
+ //#region ../api/src/event-listing-destination.ts
10992
+ const EVENT_LISTING_DESTINATION_SETTINGS_KEY = "listingDestination";
10993
+ const DEFAULT_EXTERNAL_EVENT_CTA_LABEL = "View details";
10994
+ function isRecord$10(value) {
10995
+ return value !== null && typeof value === "object" && !Array.isArray(value);
10996
+ }
10997
+ function normalizeOptionalLabel(value) {
10998
+ return typeof value === "string" && value.trim() ? value.trim() : null;
10999
+ }
11000
+ function normalizeOptionalUrl(value) {
11001
+ return typeof value === "string" ? value.trim() : "";
11002
+ }
11003
+ function normalizeEventListingDestinationInput(value) {
11004
+ if (!value || value.kind === "native") {
11005
+ return { kind: "native" };
11006
+ }
11007
+ return {
11008
+ kind: "external",
11009
+ url: normalizeOptionalUrl(value.url),
11010
+ label: normalizeOptionalLabel(value.label)
11011
+ };
11012
+ }
11013
+ function readEventListingDestinationFromSettings(settingsJson) {
11014
+ if (!isRecord$10(settingsJson)) {
11015
+ return { kind: "native" };
11016
+ }
11017
+ const rawDestination = settingsJson[EVENT_LISTING_DESTINATION_SETTINGS_KEY];
11018
+ if (!isRecord$10(rawDestination) || rawDestination.kind !== "external") {
11019
+ return { kind: "native" };
11020
+ }
11021
+ return {
11022
+ kind: "external",
11023
+ url: normalizeOptionalUrl(rawDestination.url),
11024
+ label: normalizeOptionalLabel(rawDestination.label)
11025
+ };
11026
+ }
11027
+ function serializeEventListingDestination(destination) {
11028
+ switch (destination.kind) {
11029
+ case "native": return { kind: "native" };
11030
+ case "external": return {
11031
+ kind: "external",
11032
+ url: destination.url,
11033
+ label: destination.label
11034
+ };
11035
+ }
11036
+ }
11037
+ function buildEventListingDestinationSettingsPatch(destination) {
11038
+ return { [EVENT_LISTING_DESTINATION_SETTINGS_KEY]: serializeEventListingDestination(destination) };
11039
+ }
11040
+ function parseExternalEventUrl(value) {
11041
+ const raw = normalizeOptionalUrl(value);
11042
+ if (!raw) {
11043
+ return {
11044
+ ok: false,
11045
+ error: {
11046
+ code: "missing_url",
11047
+ message: "External event URL is required."
11048
+ }
11049
+ };
11050
+ }
11051
+ try {
11052
+ const url = assertAllowedPublicUrl(raw, {
11053
+ allowedProtocols: ["https:"],
11054
+ portPolicy: "standard-only"
11055
+ });
11056
+ return {
11057
+ ok: true,
11058
+ url: url.href
11059
+ };
11060
+ } catch (error) {
11061
+ return {
11062
+ ok: false,
11063
+ error: {
11064
+ code: "invalid_url",
11065
+ message: error instanceof Error ? error.message : "External event URL must be a valid public HTTPS URL."
11066
+ }
11067
+ };
11068
+ }
11069
+ }
11070
+ function getExternalEventCtaLabel(destination) {
11071
+ return destination.label ?? DEFAULT_EXTERNAL_EVENT_CTA_LABEL;
11072
+ }
11073
+ function getEventListingDestinationPublicationIssue(destination) {
11074
+ if (destination.kind === "native") {
11075
+ return null;
11076
+ }
11077
+ const validation = parseExternalEventUrl(destination.url);
11078
+ if (validation.ok) {
11079
+ return null;
11080
+ }
11081
+ return {
11082
+ field: "listingDestination.url",
11083
+ code: validation.error.code,
11084
+ message: validation.error.message
11085
+ };
11086
+ }
11087
+ function buildPublicEventDestination(input) {
11088
+ if (input.listingDestination.kind === "native") {
11089
+ if (!input.nativePath.trim()) {
11090
+ return null;
11091
+ }
11092
+ return {
11093
+ kind: "native",
11094
+ mode: input.nativeMode,
11095
+ path: input.nativePath
11096
+ };
11097
+ }
11098
+ const validation = parseExternalEventUrl(input.listingDestination.url);
11099
+ if (!validation.ok) {
11100
+ return null;
11101
+ }
11102
+ return {
11103
+ kind: "external",
11104
+ url: validation.url,
11105
+ label: getExternalEventCtaLabel(input.listingDestination)
11106
+ };
11107
+ }
11108
+
10887
11109
  //#endregion
10888
11110
  //#region ../api/src/event-destination-policy.ts
10889
11111
  const eventDestinationModeValues = ["cms-managed", "system-managed"];
@@ -10915,6 +11137,245 @@ function macadamiaDefaultEventDestinationPolicy(selectedPresets) {
10915
11137
  };
10916
11138
  }
10917
11139
 
11140
+ //#endregion
11141
+ //#region ../api/src/appointmentServiceVisibility.ts
11142
+ const APPOINTMENT_SERVICE_VISIBILITY_VALUES = ["public", "unlisted"];
11143
+ function isAppointmentServiceVisibility(value) {
11144
+ return value === "public" || value === "unlisted";
11145
+ }
11146
+
11147
+ //#endregion
11148
+ //#region ../api/src/request-error.ts
11149
+ var ApiRequestError = class extends Error {
11150
+ constructor(message, options) {
11151
+ super(message);
11152
+ this.name = "ApiRequestError";
11153
+ this.endpoint = options.endpoint;
11154
+ this.status = options.status;
11155
+ this.method = options.method;
11156
+ this.auth = options.auth;
11157
+ this.requestId = options.requestId;
11158
+ this.body = options.body;
11159
+ this.cause = options.cause;
11160
+ this.errorCode = options.errorCode;
11161
+ this.tag = options.tag;
11162
+ this.retryAfterMs = options.retryAfterMs;
11163
+ }
11164
+ };
11165
+
11166
+ //#endregion
11167
+ //#region ../api/src/utils/isRecord.ts
11168
+ function isRecord$5(value) {
11169
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11170
+ }
11171
+
11172
+ //#endregion
11173
+ //#region ../api/src/utils/error-propagation.ts
11174
+ /**
11175
+ * Extracts an error message from an API error response body.
11176
+ * Checks for our standard API error format: { error: { message: "..." } }
11177
+ * Also handles legacy formats with `error` or `message` as strings.
11178
+ */
11179
+ function extractApiErrorMessage(body) {
11180
+ if (!isRecord$5(body)) {
11181
+ return null;
11182
+ }
11183
+ if (isRecord$5(body.error)) {
11184
+ if (typeof body.error.message === "string" && body.error.message.trim().length > 0) {
11185
+ return body.error.message;
11186
+ }
11187
+ }
11188
+ if (typeof body.error === "string" && body.error.trim().length > 0) {
11189
+ return body.error;
11190
+ }
11191
+ if (typeof body.message === "string" && body.message.trim().length > 0) {
11192
+ return body.message;
11193
+ }
11194
+ return null;
11195
+ }
11196
+ /**
11197
+ * Normalizes various error types and throws with an appropriate message.
11198
+ *
11199
+ * - For ApiRequestError: extracts message from body.error or body.message
11200
+ * - For Error instances: re-throws as-is
11201
+ * - For unknown errors: wraps with fallback message
11202
+ *
11203
+ * @param error - The error to normalize
11204
+ * @param fallback - Fallback message if no specific message can be extracted
11205
+ * @throws {Error} Always throws - this function never returns
11206
+ */
11207
+ function normalizeApiError(error, fallback) {
11208
+ if (error instanceof ApiRequestError) {
11209
+ const message = extractApiErrorMessage(error.body);
11210
+ throw new ApiRequestError(message ?? fallback, {
11211
+ endpoint: error.endpoint,
11212
+ status: error.status,
11213
+ method: error.method,
11214
+ auth: error.auth,
11215
+ requestId: error.requestId,
11216
+ body: error.body,
11217
+ cause: error,
11218
+ errorCode: error.errorCode,
11219
+ tag: error.tag,
11220
+ retryAfterMs: error.retryAfterMs
11221
+ });
11222
+ }
11223
+ if (error instanceof Error) {
11224
+ throw error;
11225
+ }
11226
+ throw new Error(fallback);
11227
+ }
11228
+
11229
+ //#endregion
11230
+ //#region ../api/src/appointmentSetup.ts
11231
+ const APPOINTMENT_SETUP_MAX_MINUTES = 1440;
11232
+ const appointmentSetupRefSchema = z.discriminatedUnion("kind", [z.object({
11233
+ kind: z.literal("existing"),
11234
+ id: z.string().min(1)
11235
+ }), z.object({
11236
+ kind: z.literal("new"),
11237
+ draftId: z.string().min(1)
11238
+ })]);
11239
+ const appointmentSetupVenueChoiceSchema = z.discriminatedUnion("kind", [
11240
+ z.object({ kind: z.literal("none") }),
11241
+ z.object({
11242
+ kind: z.literal("existing"),
11243
+ venueId: z.string().min(1)
11244
+ }),
11245
+ z.object({
11246
+ kind: z.literal("create"),
11247
+ draftId: z.string().min(1),
11248
+ title: z.string().trim().min(1),
11249
+ description: z.string().nullable(),
11250
+ timezone: z.string().trim().min(1).nullable(),
11251
+ addressVisibility: z.enum(["everyone", "booked"]).optional(),
11252
+ locationValue: locationValueSchema.nullable().optional()
11253
+ })
11254
+ ]);
11255
+ const appointmentSetupPaymentDraftSchema = z.discriminatedUnion("kind", [
11256
+ z.object({ kind: z.literal("free") }),
11257
+ z.object({ kind: z.literal("paid_online") }),
11258
+ z.object({ kind: z.literal("paid_offline") })
11259
+ ]);
11260
+ const appointmentSetupServiceAssignmentDraftSchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("all_practitioners") }), z.object({
11261
+ kind: z.literal("selected_practitioners"),
11262
+ resourceRefs: z.array(appointmentSetupRefSchema)
11263
+ })]);
11264
+ const appointmentSetupServiceDraftSchema = z.object({
11265
+ ref: appointmentSetupRefSchema,
11266
+ title: z.string().trim().min(1),
11267
+ description: z.string().nullable(),
11268
+ durationMinutes: z.number().int().positive().max(APPOINTMENT_SETUP_MAX_MINUTES),
11269
+ prepMinutes: z.number().int().nonnegative().max(APPOINTMENT_SETUP_MAX_MINUTES).default(0),
11270
+ cleanupMinutes: z.number().int().nonnegative().max(APPOINTMENT_SETUP_MAX_MINUTES).default(0),
11271
+ priceAmount: z.number().int().nonnegative().nullable(),
11272
+ isActive: z.boolean(),
11273
+ visibility: z.enum(APPOINTMENT_SERVICE_VISIBILITY_VALUES).default("public"),
11274
+ locationType: z.union([
11275
+ z.literal("virtual"),
11276
+ z.literal("in_person"),
11277
+ z.literal("hybrid")
11278
+ ]),
11279
+ venue: appointmentSetupVenueChoiceSchema,
11280
+ timezone: z.string().trim().min(1).nullable(),
11281
+ payment: appointmentSetupPaymentDraftSchema,
11282
+ assignment: appointmentSetupServiceAssignmentDraftSchema,
11283
+ categoryId: z.string().uuid().nullable().optional()
11284
+ }).superRefine((service, ctx) => {
11285
+ if (service.payment.kind !== "free" && (service.priceAmount === null || service.priceAmount <= 0)) {
11286
+ ctx.addIssue({
11287
+ code: "custom",
11288
+ path: ["priceAmount"],
11289
+ message: "Paid appointment services need a positive price."
11290
+ });
11291
+ }
11292
+ if (service.payment.kind === "free" && service.priceAmount !== null && service.priceAmount > 0) {
11293
+ ctx.addIssue({
11294
+ code: "custom",
11295
+ path: ["priceAmount"],
11296
+ message: "Free appointment services cannot carry a positive price."
11297
+ });
11298
+ }
11299
+ });
11300
+ const appointmentSetupPractitionerDraftSchema = z.object({
11301
+ ref: appointmentSetupRefSchema,
11302
+ displayName: z.string().trim().min(1),
11303
+ bio: z.string().nullable(),
11304
+ timezone: z.string().trim().min(1),
11305
+ colorHex: z.string().nullable()
11306
+ });
11307
+ const appointmentSetupWeeklyWindowDraftSchema = z.object({
11308
+ weekday: z.number().int().min(0).max(6),
11309
+ startTime: z.string(),
11310
+ endTime: z.string()
11311
+ });
11312
+ const appointmentSetupStartTimePolicyDraftSchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("automatic_spacing") }), z.object({
11313
+ kind: z.literal("regular_interval"),
11314
+ intervalMinutes: z.union([
11315
+ z.literal(10),
11316
+ z.literal(15),
11317
+ z.literal(20),
11318
+ z.literal(30),
11319
+ z.literal(45),
11320
+ z.literal(60)
11321
+ ])
11322
+ })]);
11323
+ const appointmentSetupAvailabilityDraftSchema = z.object({
11324
+ kind: z.literal("weekly_windows"),
11325
+ resourceRef: appointmentSetupRefSchema,
11326
+ serviceRef: appointmentSetupRefSchema.nullable(),
11327
+ windows: z.array(appointmentSetupWeeklyWindowDraftSchema),
11328
+ startTimePolicy: appointmentSetupStartTimePolicyDraftSchema.optional()
11329
+ });
11330
+ /**
11331
+ * Upper bounds on the booking-notice fields. Site-wide defaults — these caps
11332
+ * keep the slot engine arithmetic (`now + minutes * 60_000`) well inside safe
11333
+ * integer range and surface UI typos rather than silently storing nonsense.
11334
+ *
11335
+ * - `MIN_LEAD` capped at 28 days. A "must book at least N minutes ahead"
11336
+ * value beyond that reads as misconfiguration.
11337
+ * - `MAX_NOTICE` capped at 10 years. A reasonable ceiling for "how far in
11338
+ * the future bookings may be made"; safe against overflow.
11339
+ *
11340
+ * Mirror these in `bookingSettingsSchema` (storage layer) and the wizard
11341
+ * draft schemas — single source of truth for the contract.
11342
+ */
11343
+ const APPOINTMENT_DEFAULT_MIN_LEAD_MINUTES_MAX = 28 * 24 * 60;
11344
+ const APPOINTMENT_DEFAULT_MAX_BOOKING_NOTICE_MINUTES_MAX = 10 * 365 * 24 * 60;
11345
+ const appointmentSetupBookingNoticeSchema = z.object({
11346
+ minLeadMinutes: z.number().int().nonnegative().max(APPOINTMENT_DEFAULT_MIN_LEAD_MINUTES_MAX),
11347
+ maxBookingNoticeMinutes: z.number().int().nonnegative().max(APPOINTMENT_DEFAULT_MAX_BOOKING_NOTICE_MINUTES_MAX)
11348
+ });
11349
+ const applyAppointmentSetupRequestSchema = z.object({
11350
+ services: z.array(appointmentSetupServiceDraftSchema),
11351
+ practitioners: z.array(appointmentSetupPractitionerDraftSchema),
11352
+ availability: z.array(appointmentSetupAvailabilityDraftSchema),
11353
+ bookingNotice: appointmentSetupBookingNoticeSchema
11354
+ });
11355
+ async function getAppointmentSetupRequest(apiClient, siteId, options) {
11356
+ try {
11357
+ return unwrapResponse(await apiClient({
11358
+ endpoint: "getAppointmentSetup",
11359
+ params: { siteId },
11360
+ options
11361
+ }));
11362
+ } catch (error) {
11363
+ normalizeApiError(error, "Failed to load appointment setup");
11364
+ }
11365
+ }
11366
+ async function applyAppointmentSetupRequest(apiClient, siteId, payload, options) {
11367
+ try {
11368
+ return unwrapResponse(await apiClient({
11369
+ endpoint: "applyAppointmentSetup",
11370
+ params: { siteId },
11371
+ body: payload,
11372
+ options
11373
+ }));
11374
+ } catch (error) {
11375
+ normalizeApiError(error, "Failed to save appointment setup");
11376
+ }
11377
+ }
11378
+
10918
11379
  //#endregion
10919
11380
  //#region ../api/src/bookingConfig.ts
10920
11381
  /** Every option kind; the checkout routes validate the requested kind against it. */
@@ -11117,7 +11578,10 @@ const sdkPatchPageBlockResponseSchema = z.object({
11117
11578
  const sdkUpsertNavigationMenuResponseSchema = sdkCommandApplicationResultSchema(sdkManagementNavigationMenuSchema);
11118
11579
  const sdkUpsertSettingsResponseSchema = sdkCommandApplicationResultSchema(sdkPullSettingsSchema);
11119
11580
  const sdkUpsertFormResponseSchema = sdkCommandApplicationResultSchema(sdkPullFormSchemaWithId);
11120
- const sdkUpsertVenueResponseSchema = sdkCommandApplicationResultSchema(sdkPullVenueSchema.extend({ id: sdkIdentifierSchema }));
11581
+ const sdkUpsertVenueResponseSchema = sdkCommandApplicationResultSchema(sdkPullVenueSchema.extend({ id: sdkIdentifierSchema })).extend({ warning: z.object({
11582
+ code: z.literal("venue_public_refresh_delayed"),
11583
+ message: z.string()
11584
+ }).optional() });
11121
11585
  const sdkUpsertEventCategoryResponseSchema = sdkCommandApplicationResultSchema(sdkPullEventCategorySchema.extend({ id: sdkIdentifierSchema }));
11122
11586
  const sdkManagementEventSchema = sdkPullEventSchema.extend({
11123
11587
  id: sdkIdentifierSchema.transform(unsafeAsEventOfferingId),
@@ -11909,37 +12373,2289 @@ function createPageOperations(http) {
11909
12373
  }
11910
12374
 
11911
12375
  //#endregion
11912
- //#region ../api/src/sdkContracts.ts
11913
- const ignoredDocumentSyncSectionWarnings = {
11914
- pages: "content.pages is ignored by push-config; put pages in content/pages/*.json and use `riverbankcms push pages` (or `riverbankcms push`) to sync them.",
11915
- entries: "content.entries is ignored by push-config; put entries in content/entries/<type>/*.json and use `riverbankcms push entries` (or `riverbankcms push`) to sync them.",
11916
- navigationMenus: "content.navigationMenus is ignored by push-config; put navigation in content/navigation.json and use `riverbankcms push navigation` (or `riverbankcms push`) to sync it."
11917
- };
11918
- function formatIgnoredDocumentSyncSectionWarning(section) {
11919
- return ignoredDocumentSyncSectionWarnings[section];
12376
+ //#region ../theme-core/src/footer/mode.ts
12377
+ const footerModeSchema = z.enum([
12378
+ "default",
12379
+ "blocks",
12380
+ "default+blocks",
12381
+ "none"
12382
+ ]);
12383
+ /** Reads only SDK footer intent; unrelated config and theme fields are not authorities. */
12384
+ function resolveSdkFooterMode(config) {
12385
+ const parsed = z.object({ layout: z.object({ footerMode: footerModeSchema.optional() }).optional() }).safeParse(config);
12386
+ return parsed.success ? parsed.data.layout?.footerMode ?? "default" : "default";
12387
+ }
12388
+ const legacyFooterBrandSchema = z.object({
12389
+ variant: z.enum([
12390
+ "simple",
12391
+ "columns",
12392
+ "split",
12393
+ "centred"
12394
+ ]),
12395
+ showLogoText: z.boolean().optional(),
12396
+ logo: z.object({
12397
+ showLogo: z.boolean().optional(),
12398
+ showLogoText: z.boolean().optional()
12399
+ }).optional()
12400
+ });
12401
+ /** Offline composition backfill only. Never read appearance visibility at runtime. */
12402
+ function readLegacyFooterBrandVisibility(value) {
12403
+ const parsed = legacyFooterBrandSchema.safeParse(value);
12404
+ if (!parsed.success) return null;
12405
+ const footer = parsed.data;
12406
+ return {
12407
+ showLogo: footer.variant === "simple" ? footer.logo?.showLogo === true : footer.logo?.showLogo !== false,
12408
+ showTitle: footer.logo?.showLogoText ?? footer.showLogoText === true
12409
+ };
11920
12410
  }
11921
- const SDK_PATCH_STALE_CONTENT_FIELD = "baseContentHash";
11922
- const SDK_PATCH_STALE_CONTENT_CODE = "STALE_CONTENT";
11923
-
11924
- //#endregion
11925
- //#region ../management-client/src/blocks.ts
11926
12411
  /**
11927
- * Block Operations for Management Client
11928
- */
11929
- /**
11930
- * Discriminated stale-conflict error for `blocks.patch`: the block's draft
11931
- * changed since the caller's read. `currentHash` is the server's current
11932
- * draft content hash — retry the patch with it as `baseContentHash`.
11933
- * Extracted from the typed `SdkPatchPageBlockStaleContentFieldError` wire
11934
- * contract, so callers never parse prose.
12412
+ * Resolve what to render for the footer given the current mode and whether the
12413
+ * site is SDK-managed.
12414
+ *
12415
+ * Backwards compatible: missing/undefined mode is treated as 'default'.
11935
12416
  */
11936
- var BlockPatchStaleContentError = class extends ManagementApiError {
11937
- constructor(source, currentHash) {
11938
- super(source.message, source.code, source.details, source.statusCode, source.request);
11939
- this.kind = "stale-content";
11940
- this.name = "BlockPatchStaleContentError";
11941
- this.currentHash = currentHash;
11942
- }
12417
+ function resolveFooterRenderPlan(params) {
12418
+ if (!params.isSdkSite) {
12419
+ return {
12420
+ mode: "default",
12421
+ renderDefaultFooter: true,
12422
+ renderFooterBlocks: false
12423
+ };
12424
+ }
12425
+ const mode = params.mode ?? "default";
12426
+ switch (mode) {
12427
+ case "blocks": return {
12428
+ mode: "blocks",
12429
+ renderDefaultFooter: false,
12430
+ renderFooterBlocks: true
12431
+ };
12432
+ case "default+blocks": return {
12433
+ mode: "default+blocks",
12434
+ renderDefaultFooter: true,
12435
+ renderFooterBlocks: true
12436
+ };
12437
+ case "none": return {
12438
+ mode: "none",
12439
+ renderDefaultFooter: false,
12440
+ renderFooterBlocks: false
12441
+ };
12442
+ case "default":
12443
+ default: return {
12444
+ mode: "default",
12445
+ renderDefaultFooter: true,
12446
+ renderFooterBlocks: false
12447
+ };
12448
+ }
12449
+ }
12450
+
12451
+ //#endregion
12452
+ //#region ../blocks/src/system/node/schema.ts
12453
+ const transformStepSchema = z.object({
12454
+ id: z.string().min(1, "Transform requires an identifier"),
12455
+ options: z.record(z.string(), z.unknown()).optional()
12456
+ });
12457
+ const bindingPathSchema = z.string().min(1, "Binding path is required").regex(/[A-Za-z0-9_\.$\[\]/-]+/, "Binding path contains invalid characters");
12458
+ const bindingSchema = z.object({
12459
+ from: bindingPathSchema,
12460
+ fallback: z.unknown().optional(),
12461
+ transforms: transformStepSchema.array().default([]),
12462
+ pick: z.enum([
12463
+ "value",
12464
+ "collection",
12465
+ "context"
12466
+ ]).default("value"),
12467
+ coerce: z.enum([
12468
+ "number",
12469
+ "boolean",
12470
+ "string"
12471
+ ]).optional()
12472
+ });
12473
+ const repeatSchema = z.object({
12474
+ collection: bindingSchema,
12475
+ itemName: z.string().min(1).default("item"),
12476
+ indexName: z.string().min(1).default("index"),
12477
+ limit: z.number().int().positive().optional(),
12478
+ sortBy: z.object({
12479
+ path: bindingPathSchema,
12480
+ direction: z.enum(["asc", "desc"]).default("asc")
12481
+ }).optional()
12482
+ });
12483
+ const conditionalAuthoringPresenceSchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("visitor-only") }), z.object({
12484
+ kind: z.literal("recover-empty-when-active"),
12485
+ placeholder: z.string().min(1).optional()
12486
+ })]);
12487
+ const conditionSchema = z.object({
12488
+ when: bindingSchema,
12489
+ equals: z.unknown().optional(),
12490
+ not: z.boolean().default(false),
12491
+ authoringPresence: conditionalAuthoringPresenceSchema.default({ kind: "visitor-only" })
12492
+ });
12493
+ const dataScopeSchema = z.object({
12494
+ name: z.string().min(1),
12495
+ from: bindingSchema
12496
+ });
12497
+ const nodePropsSchema = z.record(z.string(), z.unknown()).default({});
12498
+ const nodeSchema = z.object({
12499
+ type: z.string().min(1, "Node type is required"),
12500
+ key: z.string().optional(),
12501
+ props: nodePropsSchema.optional(),
12502
+ children: z.lazy(() => nodeSchema.array().default([])).optional(),
12503
+ $bind: bindingSchema.optional(),
12504
+ $repeat: repeatSchema.optional(),
12505
+ $when: conditionSchema.optional(),
12506
+ $scopes: dataScopeSchema.array().optional()
12507
+ });
12508
+ const nodeTreeSchema = nodeSchema.array().or(nodeSchema);
12509
+ const NodeSchema = nodeSchema;
12510
+ const NodeTreeSchema = nodeTreeSchema;
12511
+
12512
+ //#endregion
12513
+ //#region ../blocks/src/system/data/loaderEndpoints.ts
12514
+ const SUPPORTED_LOADER_ENDPOINTS = [
12515
+ "listPublishedEntries",
12516
+ "getPublishedEntryPreview",
12517
+ "listPublicEvents",
12518
+ "listPublicEventCalendarWindow",
12519
+ "listPublicRecurringSchedule",
12520
+ "getPublicEventDetails",
12521
+ "listPublicRouteCandidates",
12522
+ "listPublicTeamMembers",
12523
+ "listPublicProducts",
12524
+ "listPublicPasses",
12525
+ "listPublicMemberships",
12526
+ "getPublicFormById",
12527
+ "getPublicBookingServices",
12528
+ "getPublicWeeklySlotAvailability"
12529
+ ];
12530
+ function isSupportedLoaderEndpoint(endpoint) {
12531
+ return SUPPORTED_LOADER_ENDPOINTS.includes(endpoint);
12532
+ }
12533
+
12534
+ //#endregion
12535
+ //#region ../blocks/src/system/constants/blockKinds.ts
12536
+ const EVENT_DISPLAY_BLOCK_KINDS = [
12537
+ "block.event-details",
12538
+ "block.event-spotlight",
12539
+ "block.event-listing",
12540
+ "block.event-calendar",
12541
+ "block.recurring-schedule",
12542
+ "block.offerings-preview"
12543
+ ];
12544
+ const SYSTEM_BLOCK_KINDS = [
12545
+ "block.hero",
12546
+ "block.intro",
12547
+ "block.columns",
12548
+ "block.collection",
12549
+ "block.feature-cards",
12550
+ "block.media-text",
12551
+ "block.cta-full",
12552
+ "block.single-button",
12553
+ "block.body-text",
12554
+ "block.location-map",
12555
+ "block.faq",
12556
+ "block.testimonials",
12557
+ "block.team-members",
12558
+ "block.embed",
12559
+ "block.blog-post",
12560
+ "block.blog-listing",
12561
+ "block.site-header",
12562
+ "block.site-footer",
12563
+ "block.form",
12564
+ "block.contact-newsletter",
12565
+ "block.appointment-booking",
12566
+ "block.appointment-services",
12567
+ "block.weekly-slot-availability",
12568
+ "block.event-registration",
12569
+ ...EVENT_DISPLAY_BLOCK_KINDS,
12570
+ "block.shop",
12571
+ "block.product-list",
12572
+ "block.product-detail",
12573
+ "block.cart",
12574
+ "block.checkout",
12575
+ "block.gifting",
12576
+ "block.newsletter-signup",
12577
+ "block.image-gallery",
12578
+ "block.video-grid",
12579
+ "block.audio-player",
12580
+ "block.file-download"
12581
+ ];
12582
+
12583
+ //#endregion
12584
+ //#region ../blocks/src/system/node/typeBasedLayout.ts
12585
+ /**
12586
+ * Creates conditional layout branches based on item type.
12587
+ * Used for polymorphic repeaters where each item type has a different layout.
12588
+ *
12589
+ * @param itemTypesMap - Map of type IDs to their layout definitions
12590
+ * @param options - Configuration options
12591
+ * @returns Array of conditional node definitions
12592
+ *
12593
+ * @example
12594
+ * ```typescript
12595
+ * // In a repeater with items: [{_type: 'card', ...}, {_type: 'quote', ...}]
12596
+ * const children = typeBasedLayout({
12597
+ * card: stack({}, [
12598
+ * text({ as: 'h3' }, bind('item.title')),
12599
+ * richText({}, bind('item.body'))
12600
+ * ]),
12601
+ * quote: stack({}, [
12602
+ * richText({}, bind('item.text')),
12603
+ * text({ className: 'author' }, bind('item.author'))
12604
+ * ])
12605
+ * }, { itemName: 'item' });
12606
+ * ```
12607
+ */
12608
+ function typeBasedLayout(itemTypesMap, options) {
12609
+ const itemName = options?.itemName ?? "item";
12610
+ const result = Object.entries(itemTypesMap).map(([typeId, layout]) => {
12611
+ const node = Array.isArray(layout) ? layout.length === 0 ? {
12612
+ type: "div",
12613
+ children: []
12614
+ } : layout.length === 1 ? layout[0] ?? {
12615
+ type: "div",
12616
+ children: []
12617
+ } : {
12618
+ type: "div",
12619
+ children: layout
12620
+ } : layout;
12621
+ return {
12622
+ ...node,
12623
+ $when: {
12624
+ when: { from: `${itemName}._type` },
12625
+ equals: typeId
12626
+ }
12627
+ };
12628
+ });
12629
+ return result;
12630
+ }
12631
+
12632
+ //#endregion
12633
+ //#region ../blocks/src/system/node/validation-constants.ts
12634
+ /**
12635
+ * Constants for validating declarative node layouts.
12636
+ *
12637
+ * These whitelists define which node types and transform IDs
12638
+ * are allowed in custom block layouts.
12639
+ */
12640
+ /**
12641
+ * Valid node types for declarative layouts.
12642
+ *
12643
+ * This whitelist defines which component types can be used in
12644
+ * SdkCustomBlock.layout definitions. Node types not in this list
12645
+ * will be rejected during validation.
12646
+ */
12647
+ const ALLOWED_NODE_TYPES = [
12648
+ "div",
12649
+ "figure",
12650
+ "section",
12651
+ "headerSection",
12652
+ "stack",
12653
+ "inline",
12654
+ "grid",
12655
+ "text",
12656
+ "richText",
12657
+ "media",
12658
+ "videoEmbed",
12659
+ "icon",
12660
+ "feature-card-icon",
12661
+ "svg",
12662
+ "button",
12663
+ "link",
12664
+ "carousel",
12665
+ "accordion",
12666
+ "accordionItem",
12667
+ "form",
12668
+ "booking-form",
12669
+ "weekly-slot-availability",
12670
+ "event-registration",
12671
+ "event-spotlight",
12672
+ "event-listing",
12673
+ "event-calendar",
12674
+ "offerings-preview",
12675
+ "event-combined",
12676
+ "shop",
12677
+ "product-list",
12678
+ "product-detail",
12679
+ "cart",
12680
+ "checkout",
12681
+ "gifting"
12682
+ ];
12683
+ /**
12684
+ * Valid transform IDs for declarative layouts.
12685
+ *
12686
+ * This whitelist defines which transforms can be referenced in
12687
+ * $bind.transforms within custom block layouts. Transform IDs
12688
+ * not in this list will be rejected during validation.
12689
+ */
12690
+ const ALLOWED_TRANSFORM_IDS = [
12691
+ "string.uppercase",
12692
+ "value.fallback",
12693
+ "date.formatShort",
12694
+ "array.length",
12695
+ "value.jsonArray",
12696
+ "value.csv",
12697
+ "array.marqueeLoop",
12698
+ "value.mapToken",
12699
+ "layout.maxWidthClass",
12700
+ "layout.headerRootClass",
12701
+ "layout.footerRootClass",
12702
+ "layout.headerRootStyle",
12703
+ "layout.headerSectionBackground",
12704
+ "layout.footerRootStyle",
12705
+ "ui.headerNavLinkClass",
12706
+ "ui.headingClassFromAlignment",
12707
+ "ui.bodyClassFromAlignment",
12708
+ "ui.stackAlignFromAlignment",
12709
+ "ui.sectionStateTone",
12710
+ "ui.heroContentWrapperClass",
12711
+ "ui.heroCtaJustify",
12712
+ "ui.heroStackJustifyFromVerticalAlignment",
12713
+ "ui.heroGridClassFromVerticalAlignment",
12714
+ "ui.conditionalClass",
12715
+ "ui.twoColumnContainerClass",
12716
+ "ui.twoColumnFlexBasis",
12717
+ "ui.imageGalleryFilterClass",
12718
+ "ui.imageGalleryTileClass",
12719
+ "ui.imageGalleryTileStyle",
12720
+ "ui.imageGalleryMarqueeTileStyle",
12721
+ "ui.imageGalleryMarqueeDuration",
12722
+ "ui.imageGalleryMarqueeDirection",
12723
+ "ui.imageFragmentClass",
12724
+ "ui.imageFragmentSize",
12725
+ "media.fromUrl",
12726
+ "background.resolveStyle",
12727
+ "background.resolveColorsStyle",
12728
+ "background.resolveFrameClassName",
12729
+ "background.resolveImageStyle",
12730
+ "background.resolveImageClassName",
12731
+ "background.resolveOverlayStyle",
12732
+ "links.resolve",
12733
+ "links.label",
12734
+ "content.interpolateRichTextTokens",
12735
+ "boxStyles.spacing",
12736
+ "boxStyles.resolveRootClass",
12737
+ "boxStyles.resolveRootStyle",
12738
+ "boxStyles.semanticGap",
12739
+ "boxStyles.semanticGapValue",
12740
+ "containerStyles.resolveBoxClassName"
12741
+ ];
12742
+ /**
12743
+ * Set for O(1) lookup of allowed node types.
12744
+ */
12745
+ const ALLOWED_NODE_TYPES_SET = new Set(ALLOWED_NODE_TYPES);
12746
+ /**
12747
+ * Set for O(1) lookup of allowed transform IDs.
12748
+ */
12749
+ const ALLOWED_TRANSFORM_IDS_SET = new Set(ALLOWED_TRANSFORM_IDS);
12750
+ /**
12751
+ * Check if a node type is allowed in layouts.
12752
+ */
12753
+ function isAllowedNodeType(type) {
12754
+ return ALLOWED_NODE_TYPES_SET.has(type);
12755
+ }
12756
+ /**
12757
+ * Check if a transform ID is allowed in layouts.
12758
+ */
12759
+ function isAllowedTransformId(id) {
12760
+ return ALLOWED_TRANSFORM_IDS_SET.has(id);
12761
+ }
12762
+
12763
+ //#endregion
12764
+ //#region ../blocks/src/system/layout/repeaterGridModel.ts
12765
+ const DEFAULT_REPEATER_GRID_SPAN_FIELD_ID = "gridSpan";
12766
+ const DEFAULT_REPEATER_GRID_ROW_FIELD_ID = "gridRow";
12767
+ const DEFAULT_REPEATER_GRID_COLUMN_FIELD_ID = "gridColumn";
12768
+ const MIN_COLUMNS = 1;
12769
+ const MAX_REPEATER_GRID_COLUMNS = 6;
12770
+ const MAX_COLUMNS = MAX_REPEATER_GRID_COLUMNS;
12771
+ function normalizeInteger(value) {
12772
+ if (typeof value === "number" && Number.isFinite(value)) {
12773
+ return Math.trunc(value);
12774
+ }
12775
+ if (typeof value === "string") {
12776
+ const trimmed = value.trim();
12777
+ if (!trimmed) return null;
12778
+ const parsed = Number(trimmed);
12779
+ if (!Number.isFinite(parsed)) return null;
12780
+ return Math.trunc(parsed);
12781
+ }
12782
+ return null;
12783
+ }
12784
+ function clamp(value, min, max) {
12785
+ return Math.max(min, Math.min(max, value));
12786
+ }
12787
+ function asNonEmptyString(value) {
12788
+ if (typeof value !== "string") return null;
12789
+ const trimmed = value.trim();
12790
+ return trimmed.length > 0 ? trimmed : null;
12791
+ }
12792
+ function resolveRepeaterGridSpanFieldId(options) {
12793
+ return asNonEmptyString(options?.id) ?? DEFAULT_REPEATER_GRID_SPAN_FIELD_ID;
12794
+ }
12795
+ function resolveRepeaterGridRowFieldId(options) {
12796
+ return asNonEmptyString(options?.id) ?? DEFAULT_REPEATER_GRID_ROW_FIELD_ID;
12797
+ }
12798
+ function resolveRepeaterGridColumnFieldId(options) {
12799
+ return asNonEmptyString(options?.id) ?? DEFAULT_REPEATER_GRID_COLUMN_FIELD_ID;
12800
+ }
12801
+ function getBreakpointFallbackOrder(breakpoint) {
12802
+ if (breakpoint === "xl") return [
12803
+ "xl",
12804
+ "lg",
12805
+ "md",
12806
+ "sm",
12807
+ "base"
12808
+ ];
12809
+ if (breakpoint === "lg") return [
12810
+ "lg",
12811
+ "md",
12812
+ "sm",
12813
+ "base"
12814
+ ];
12815
+ if (breakpoint === "md") return [
12816
+ "md",
12817
+ "sm",
12818
+ "base",
12819
+ "lg"
12820
+ ];
12821
+ if (breakpoint === "sm") return [
12822
+ "sm",
12823
+ "base",
12824
+ "lg"
12825
+ ];
12826
+ return ["base", "lg"];
12827
+ }
12828
+ function resolveRepeaterGridBreakpointForWidth(width) {
12829
+ if (!Number.isFinite(width)) {
12830
+ return "lg";
12831
+ }
12832
+ if (width >= BREAKPOINT_XL) return "xl";
12833
+ if (width >= BREAKPOINT_LG) return "lg";
12834
+ if (width >= BREAKPOINT_MD) return "md";
12835
+ if (width >= BREAKPOINT_SM) return "sm";
12836
+ return "base";
12837
+ }
12838
+ function createRepeaterGridSpanField(options) {
12839
+ const fieldId = resolveRepeaterGridSpanFieldId(options);
12840
+ const label = asNonEmptyString(options?.label) ?? "Grid span";
12841
+ const description = asNonEmptyString(options?.description) ?? "Columns occupied by this item in layout-grid mode.";
12842
+ const defaultSpan = clamp(normalizeRepeaterGridColumns(options?.defaultSpan ?? 1), 1, 4);
12843
+ return {
12844
+ id: fieldId,
12845
+ type: "select",
12846
+ label,
12847
+ description,
12848
+ options: [
12849
+ {
12850
+ value: "1",
12851
+ label: "1 column"
12852
+ },
12853
+ {
12854
+ value: "2",
12855
+ label: "2 columns"
12856
+ },
12857
+ {
12858
+ value: "3",
12859
+ label: "3 columns"
12860
+ },
12861
+ {
12862
+ value: "4",
12863
+ label: "4 columns"
12864
+ }
12865
+ ],
12866
+ defaultValue: String(defaultSpan)
12867
+ };
12868
+ }
12869
+ function createRepeaterGridRowField(options) {
12870
+ const fieldId = resolveRepeaterGridRowFieldId(options);
12871
+ const label = asNonEmptyString(options?.label) ?? "Grid row";
12872
+ const description = asNonEmptyString(options?.description) ?? "Manual layout row for this item.";
12873
+ const defaultRow = normalizeRepeaterGridRow(options?.defaultRow ?? 1);
12874
+ return {
12875
+ id: fieldId,
12876
+ type: "number",
12877
+ label,
12878
+ description,
12879
+ defaultValue: defaultRow,
12880
+ ui: { hidden: true }
12881
+ };
12882
+ }
12883
+ function createRepeaterGridColumnField(options) {
12884
+ const fieldId = resolveRepeaterGridColumnFieldId(options);
12885
+ const label = asNonEmptyString(options?.label) ?? "Grid column";
12886
+ const description = asNonEmptyString(options?.description) ?? "Manual layout starting column for this item.";
12887
+ const defaultColumn = normalizeRepeaterGridColumns(options?.defaultColumn ?? 1);
12888
+ return {
12889
+ id: fieldId,
12890
+ type: "number",
12891
+ label,
12892
+ description,
12893
+ defaultValue: defaultColumn,
12894
+ ui: { hidden: true }
12895
+ };
12896
+ }
12897
+ function withRepeaterGridSpanField(fields, options) {
12898
+ const spanFieldId = resolveRepeaterGridSpanFieldId(options);
12899
+ if (fields.some((field) => field.id === spanFieldId)) {
12900
+ return [...fields];
12901
+ }
12902
+ return [createRepeaterGridSpanField(options), ...fields];
12903
+ }
12904
+ function withRepeaterGridPositionFields(fields, options) {
12905
+ const rowFieldId = resolveRepeaterGridRowFieldId(options?.row);
12906
+ const withRow = fields.some((field) => field.id === rowFieldId) ? [...fields] : [createRepeaterGridRowField(options?.row), ...fields];
12907
+ const columnFieldId = resolveRepeaterGridColumnFieldId(options?.column);
12908
+ return withRow.some((field) => field.id === columnFieldId) ? withRow : [createRepeaterGridColumnField(options?.column), ...withRow];
12909
+ }
12910
+ function withRepeaterGridSpanFieldOnItemTypes(itemTypes, options) {
12911
+ const entries = Object.entries(itemTypes).map(([typeKey, itemType]) => {
12912
+ const nextFields = withRepeaterGridSpanField(itemType.fields, options);
12913
+ return [typeKey, {
12914
+ ...itemType,
12915
+ fields: nextFields
12916
+ }];
12917
+ });
12918
+ return Object.fromEntries(entries);
12919
+ }
12920
+ function withRepeaterGridPositionFieldsOnItemTypes(itemTypes, options) {
12921
+ const entries = Object.entries(itemTypes).map(([typeKey, itemType]) => {
12922
+ const nextFields = withRepeaterGridPositionFields(itemType.fields, options);
12923
+ return [typeKey, {
12924
+ ...itemType,
12925
+ fields: nextFields
12926
+ }];
12927
+ });
12928
+ return Object.fromEntries(entries);
12929
+ }
12930
+ function normalizeRepeaterGridColumns(value) {
12931
+ const parsed = normalizeInteger(value);
12932
+ if (parsed == null) {
12933
+ return MIN_COLUMNS;
12934
+ }
12935
+ return clamp(parsed, MIN_COLUMNS, MAX_COLUMNS);
12936
+ }
12937
+ function normalizeRepeaterGridRow(value) {
12938
+ const parsed = normalizeInteger(value);
12939
+ if (parsed == null) {
12940
+ return 1;
12941
+ }
12942
+ return Math.max(1, parsed);
12943
+ }
12944
+ function resolveRepeaterGridColumnsForBreakpoint(values, bindings, breakpoint) {
12945
+ const order = getBreakpointFallbackOrder(breakpoint);
12946
+ for (const candidate of order) {
12947
+ const fieldId = asNonEmptyString(bindings[candidate]);
12948
+ if (!fieldId) continue;
12949
+ const resolved = normalizeInteger(values[fieldId]);
12950
+ if (resolved == null) continue;
12951
+ return clamp(resolved, MIN_COLUMNS, MAX_COLUMNS);
12952
+ }
12953
+ return MIN_COLUMNS;
12954
+ }
12955
+ /** Strict admission; rendering normalization never grants permission to persist. */
12956
+ function parseRepeaterGridInteger(value) {
12957
+ if (typeof value !== "number" && (typeof value !== "string" || !value.trim())) return null;
12958
+ const number = Number(value);
12959
+ return Number.isInteger(number) ? number : null;
12960
+ }
12961
+ function isAcceptedRepeaterGridSpan(value, columns, acceptedSpans) {
12962
+ const span = parseRepeaterGridInteger(value);
12963
+ return span != null && span >= 1 && span <= columns && (!acceptedSpans || acceptedSpans.includes(span));
12964
+ }
12965
+ function resolveRepeaterGridAcceptedSpans(fields, spanField) {
12966
+ const field = fields.find((candidate) => candidate.id === spanField);
12967
+ return field?.type === "select" ? (field.options ?? []).map((option) => Number(option.value)) : undefined;
12968
+ }
12969
+ function normalizeRepeaterGridSpan(value, activeColumns, acceptedSpans) {
12970
+ if (acceptedSpans) {
12971
+ const valid = acceptedSpans.filter((span) => span <= activeColumns);
12972
+ const parsed$1 = parseRepeaterGridInteger(value);
12973
+ return parsed$1 != null && valid.includes(parsed$1) ? parsed$1 : valid.length ? Math.min(...valid) : 1;
12974
+ }
12975
+ const parsed = normalizeInteger(value);
12976
+ if (parsed == null) {
12977
+ return 1;
12978
+ }
12979
+ return clamp(parsed, 1, Math.max(1, activeColumns));
12980
+ }
12981
+ function normalizeRepeaterGridColStart(value, activeColumns, span) {
12982
+ const parsed = normalizeInteger(value);
12983
+ const normalizedColumns = Math.max(1, activeColumns);
12984
+ const normalizedSpan = clamp(span, 1, normalizedColumns);
12985
+ const maxColStart = Math.max(1, normalizedColumns - normalizedSpan + 1);
12986
+ if (parsed == null) {
12987
+ return 1;
12988
+ }
12989
+ return clamp(parsed, 1, maxColStart);
12990
+ }
12991
+ function deriveRepeaterGridMaxRowsForBreakpoint(input) {
12992
+ const { desktopColumns, desktopMaxRows, activeColumns } = input;
12993
+ if (desktopMaxRows == null) {
12994
+ return undefined;
12995
+ }
12996
+ const normalizedDesktopColumns = Math.max(1, desktopColumns);
12997
+ const normalizedActiveColumns = Math.max(1, activeColumns);
12998
+ const capacityCells = normalizedDesktopColumns * desktopMaxRows;
12999
+ return Math.max(1, Math.ceil(capacityCells / normalizedActiveColumns));
13000
+ }
13001
+ function toRowCellKey(row, col, columns) {
13002
+ return (row - 1) * columns + col;
13003
+ }
13004
+ function buildPlacementCells(placement) {
13005
+ return Array.from({ length: placement.span }, (_, index) => placement.colStart + index);
13006
+ }
13007
+ function buildOccupancyByCell(placements, columns) {
13008
+ const entries = placements.flatMap((placement) => buildPlacementCells(placement).map((col) => [toRowCellKey(placement.row, col, columns), placement.id]));
13009
+ const occupancy = entries.reduce((cells, [cell, id]) => new Map(cells).set(cell, new Set([...cells.get(cell) ?? [], id])), new Map());
13010
+ return {
13011
+ occupancy,
13012
+ hasCollisions: Array.from(occupancy.values()).some((ids) => ids.size > 1)
13013
+ };
13014
+ }
13015
+ function getOverlappingItemIds(input) {
13016
+ return new Set(buildPlacementCells(input).flatMap((col) => Array.from(input.occupancy.get(toRowCellKey(input.row, col, input.columns)) ?? []).filter((id) => !input.excludedIds?.has(id))));
13017
+ }
13018
+ function normalizeInputItem(item, columns, sourceIndex) {
13019
+ const span = normalizeRepeaterGridSpan(item.span, columns, item.acceptedSpans);
13020
+ const row = normalizeRepeaterGridRow(item.row);
13021
+ const colStart = normalizeRepeaterGridColStart(item.colStart, columns, span);
13022
+ const colEnd = colStart + span - 1;
13023
+ return {
13024
+ id: item.id,
13025
+ startNewRow: item.startNewRow,
13026
+ acceptedSpans: item.acceptedSpans,
13027
+ sourceIndex,
13028
+ span,
13029
+ row,
13030
+ colStart,
13031
+ colEnd,
13032
+ startCell: toRowCellKey(row, colStart, columns)
13033
+ };
13034
+ }
13035
+ /** Ordered, non-dense placement. Rendering clamps widths; admission checks originals separately. */
13036
+ function flowRepeaterGridItems(items, columns) {
13037
+ const count = normalizeRepeaterGridColumns(columns);
13038
+ return items.reduce((placed, item) => {
13039
+ const span = normalizeRepeaterGridSpan(item.span, count);
13040
+ const previous = placed.at(-1);
13041
+ const nextColumn = previous ? previous.colStart + previous.span : 1;
13042
+ const wraps = previous != null && (item.startNewRow === true || nextColumn + span - 1 > count);
13043
+ return [...placed, {
13044
+ ...item,
13045
+ span,
13046
+ row: (previous?.row ?? 1) + (wraps ? 1 : 0),
13047
+ colStart: wraps ? 1 : nextColumn
13048
+ }];
13049
+ }, []);
13050
+ }
13051
+ /** @internal */
13052
+ function orderedGridItems(layout) {
13053
+ return [...layout.placements].sort((a, b) => a.sourceIndex - b.sourceIndex);
13054
+ }
13055
+ function isRepeaterGridLayoutInvalid(layout) {
13056
+ return layout.hasInvalidItems || layout.hasCollisions || layout.overCapacity;
13057
+ }
13058
+ function hasInvalidGridItems(items, columns, policy) {
13059
+ const flow = policy === "flow" ? flowRepeaterGridItems(items, columns) : null;
13060
+ return items.some((item, index) => !isValidGridItemGeometry(item, columns) || flow != null && (item.row !== flow[index]?.row || item.colStart !== flow[index]?.colStart));
13061
+ }
13062
+ function isValidGridItemGeometry(item, columns) {
13063
+ return isAcceptedRepeaterGridSpan(item.span, columns, item.acceptedSpans) && parseRepeaterGridInteger(item.row) != null && item.row >= 1 && parseRepeaterGridInteger(item.colStart) != null && item.colStart >= 1 && item.colStart + item.span - 1 <= columns;
13064
+ }
13065
+ function findFlowAppendPlacement(layout, span, startNewRow) {
13066
+ const next = flowRepeaterGridItems([...orderedGridItems(layout), {
13067
+ id: "new",
13068
+ span,
13069
+ row: 1,
13070
+ colStart: 1,
13071
+ startNewRow
13072
+ }], layout.columns).at(-1);
13073
+ return !isRepeaterGridLayoutInvalid(layout) && next && (layout.maxRows == null || next.row <= layout.maxRows) ? {
13074
+ row: next.row,
13075
+ colStart: next.colStart
13076
+ } : null;
13077
+ }
13078
+ /** @internal */
13079
+ function planFlowItems(layout, items) {
13080
+ const placed = flowRepeaterGridItems(items, layout.columns);
13081
+ if (isRepeaterGridLayoutInvalid(layout) || placed.some((item) => !isAcceptedRepeaterGridSpan(item.span, layout.columns, item.acceptedSpans) || layout.maxRows != null && item.row > layout.maxRows)) return { kind: "blocked" };
13082
+ const [first, ...rest] = placed.map(({ id, row, colStart, span }) => ({
13083
+ id,
13084
+ row,
13085
+ colStart,
13086
+ span
13087
+ }));
13088
+ return first ? {
13089
+ kind: "move",
13090
+ updates: [first, ...rest]
13091
+ } : { kind: "unchanged" };
13092
+ }
13093
+ /** Insertion reorder, with a complete coordinate write for the resulting order. */
13094
+ function planRepeaterGridReorder(layout, id, to, interaction = "step") {
13095
+ if (layout.placementPolicy !== "flow") return { kind: "blocked" };
13096
+ const items = orderedGridItems(layout);
13097
+ const from = items.findIndex((item) => item.id === id);
13098
+ if (from < 0 || to < 0 || to >= items.length) return { kind: "blocked" };
13099
+ if (from === to) return { kind: "unchanged" };
13100
+ const active = items[from];
13101
+ if (!active) return { kind: "blocked" };
13102
+ return decideFlowReorder({
13103
+ layout,
13104
+ items,
13105
+ active,
13106
+ from,
13107
+ to,
13108
+ interaction
13109
+ });
13110
+ }
13111
+ function decideFlowReorder(input) {
13112
+ const { layout, items, active, from, to, interaction } = input;
13113
+ const id = active.id;
13114
+ const remaining = items.filter((item) => item.id !== id);
13115
+ const inserted = [
13116
+ ...remaining.slice(0, to),
13117
+ active,
13118
+ ...remaining.slice(to)
13119
+ ];
13120
+ const clearRowBreak = interaction === "drag" && active.startNewRow === true && dragInsertsWithinRow(items, from, to, layout.columns);
13121
+ const nextItems = clearRowBreak ? inserted.map((item) => item.id === id ? {
13122
+ ...item,
13123
+ startNewRow: false
13124
+ } : item) : inserted;
13125
+ const decision = planFlowItems(layout, nextItems);
13126
+ return decision.kind === "move" ? {
13127
+ ...decision,
13128
+ reorder: {
13129
+ id,
13130
+ from,
13131
+ to
13132
+ },
13133
+ ...clearRowBreak ? { startNewRowUpdate: {
13134
+ id,
13135
+ value: false
13136
+ } } : {}
13137
+ } : decision;
13138
+ }
13139
+ /** Read the insertion bar position, independently of the dragged width. */
13140
+ function dragInsertsWithinRow(items, from, to, columns) {
13141
+ const target = items[to];
13142
+ if (!target) return false;
13143
+ const column = from < to ? target.colEnd + 1 : target.colStart;
13144
+ return column > 1 && column <= columns;
13145
+ }
13146
+ function planRepeaterGridStartNewRow(layout, id, value) {
13147
+ const active = layout.byId[id];
13148
+ if (layout.placementPolicy !== "flow" || !active) return { kind: "blocked" };
13149
+ const decision = planFlowItems(layout, orderedGridItems(layout).map((item) => item.id === id ? {
13150
+ ...item,
13151
+ startNewRow: value
13152
+ } : item));
13153
+ return decision.kind === "move" ? {
13154
+ ...decision,
13155
+ startNewRowUpdate: {
13156
+ id,
13157
+ value
13158
+ }
13159
+ } : decision;
13160
+ }
13161
+ function buildRepeaterGridLayout(input) {
13162
+ const columns = normalizeRepeaterGridColumns(input.columns);
13163
+ const maxRows = input.maxRows != null ? Math.max(1, Math.trunc(input.maxRows)) : undefined;
13164
+ const capacityCells = maxRows != null ? maxRows * columns : undefined;
13165
+ const placements = input.items.map((item, sourceIndex) => normalizeInputItem(item, columns, sourceIndex)).sort((a, b) => {
13166
+ if (a.row !== b.row) return a.row - b.row;
13167
+ if (a.colStart !== b.colStart) return a.colStart - b.colStart;
13168
+ return a.sourceIndex - b.sourceIndex;
13169
+ });
13170
+ const byId = Object.fromEntries(placements.map((placement) => [placement.id, placement]));
13171
+ const maxRowUsed = placements.length > 0 ? Math.max(...placements.map((placement) => placement.row)) : 0;
13172
+ const { hasCollisions } = buildOccupancyByCell(placements, columns);
13173
+ const overCapacity = maxRows != null && maxRowUsed > maxRows;
13174
+ return {
13175
+ placementPolicy: input.placementPolicy,
13176
+ sourceItems: input.items.map((item) => ({ ...item })),
13177
+ columns,
13178
+ maxRows,
13179
+ capacityCells,
13180
+ maxRowUsed,
13181
+ overCapacity,
13182
+ hasCollisions,
13183
+ hasInvalidItems: hasInvalidGridItems(input.items, columns, input.placementPolicy),
13184
+ placements,
13185
+ byId
13186
+ };
13187
+ }
13188
+ function findFirstAvailableRepeaterGridPlacement(input) {
13189
+ const columns = normalizeRepeaterGridColumns(input.columns);
13190
+ const maxRows = input.maxRows != null ? Math.max(1, Math.trunc(input.maxRows)) : undefined;
13191
+ if (!isAcceptedRepeaterGridSpan(input.newItemSpan, columns, input.acceptedSpans)) return null;
13192
+ const span = normalizeRepeaterGridSpan(input.newItemSpan, columns, input.acceptedSpans);
13193
+ const layout = buildRepeaterGridLayout({
13194
+ items: input.items,
13195
+ columns,
13196
+ maxRows,
13197
+ placementPolicy: input.placementPolicy
13198
+ });
13199
+ if (input.placementPolicy === "flow") return findFlowAppendPlacement(layout, span, input.startNewRow);
13200
+ const { occupancy } = buildOccupancyByCell(layout.placements, columns);
13201
+ const rowLimit = maxRows ?? Math.max(1, layout.maxRowUsed + 1);
13202
+ if (isRepeaterGridLayoutInvalid(layout)) return null;
13203
+ const maxColStart = columns - span + 1;
13204
+ return Array.from({ length: rowLimit * maxColStart }, (_, index) => ({
13205
+ row: Math.floor(index / maxColStart) + 1,
13206
+ colStart: index % maxColStart + 1
13207
+ })).find(({ row, colStart }) => getOverlappingItemIds({
13208
+ occupancy,
13209
+ row,
13210
+ colStart,
13211
+ span,
13212
+ columns
13213
+ }).size === 0) ?? null;
13214
+ }
13215
+ function canAppendRepeaterGridItem(input) {
13216
+ return findFirstAvailableRepeaterGridPlacement({
13217
+ items: input.items,
13218
+ newItemSpan: input.newItemSpan,
13219
+ acceptedSpans: input.acceptedSpans,
13220
+ columns: input.columns,
13221
+ maxRows: input.maxRows
13222
+ }) != null;
13223
+ }
13224
+
13225
+ //#endregion
13226
+ //#region ../blocks/src/system/layout/repeaterGrid.ts
13227
+ function placementsOverlap(left, right) {
13228
+ return left.row === right.row && left.colStart < right.colStart + right.span && right.colStart < left.colStart + left.span;
13229
+ }
13230
+ function swapPositionUpdates(active, target) {
13231
+ const adjacent = active.row === target.row && (active.colEnd + 1 === target.colStart || target.colEnd + 1 === active.colStart);
13232
+ const activeCol = adjacent && active.colStart < target.colStart ? active.colStart + target.span : target.colStart;
13233
+ const targetCol = adjacent && target.colStart < active.colStart ? target.colStart + active.span : active.colStart;
13234
+ return [{
13235
+ id: active.id,
13236
+ row: target.row,
13237
+ colStart: activeCol,
13238
+ span: active.span
13239
+ }, {
13240
+ id: target.id,
13241
+ row: active.row,
13242
+ colStart: targetCol,
13243
+ span: target.span
13244
+ }];
13245
+ }
13246
+ /** Admission retains raw authoring geometry; display-only callers may supply placements alone. */
13247
+ function sourceGridItems(layout) {
13248
+ return layout.sourceItems ?? layout.placements;
13249
+ }
13250
+ /** Repair edits may remove existing defects, but cannot introduce another one. */
13251
+ function gridDefects(layout) {
13252
+ const geometry = sourceGridItems(layout).flatMap((item) => [
13253
+ ...!isAcceptedRepeaterGridSpan(item.span, layout.columns, item.acceptedSpans) ? [`span:${item.id}`] : [],
13254
+ ...!Number.isInteger(item.row) || item.row < 1 ? [`row:${item.id}`] : [],
13255
+ ...!Number.isInteger(item.colStart) || item.colStart < 1 || item.colStart + item.span - 1 > layout.columns ? [`column:${item.id}`] : []
13256
+ ]);
13257
+ const collisions = layout.placements.flatMap((item, index) => layout.placements.slice(index + 1).filter((other) => placementsOverlap(item, other)).map((other) => `collision:${[item.id, other.id].sort().join(":")}`));
13258
+ const capacity = layout.placements.filter((item) => layout.maxRows != null && item.row > layout.maxRows).map((item) => `capacity:${item.id}`);
13259
+ return new Set([
13260
+ ...geometry,
13261
+ ...collisions,
13262
+ ...capacity,
13263
+ ...(layout.invalidCoordinateIds ?? []).map((id) => `coordinate:${id}`)
13264
+ ]);
13265
+ }
13266
+ function validateGridMove(layout, updates) {
13267
+ const items = sourceGridItems(layout).map((item) => ({
13268
+ ...item,
13269
+ ...updates.find((update) => update.id === item.id)
13270
+ }));
13271
+ const candidate = buildRepeaterGridLayout({
13272
+ items,
13273
+ columns: layout.columns,
13274
+ maxRows: layout.maxRows,
13275
+ placementPolicy: layout.placementPolicy
13276
+ });
13277
+ const unresolved = (layout.invalidCoordinateIds ?? []).filter((id) => !updates.some((update) => update.id === id));
13278
+ const before = gridDefects(layout);
13279
+ const after = gridDefects({
13280
+ ...candidate,
13281
+ invalidCoordinateIds: unresolved
13282
+ });
13283
+ const valid = before.size === 0 ? after.size === 0 : after.size < before.size && [...after].every((defect) => before.has(defect));
13284
+ return valid ? {
13285
+ kind: "move",
13286
+ updates
13287
+ } : { kind: "blocked" };
13288
+ }
13289
+ function decideGridDrop(layout, active, destination) {
13290
+ const occupants = layout.placements.filter((placement) => placement.id !== active.id && placement.row === destination.row && destination.colStart >= placement.colStart && destination.colStart <= placement.colEnd);
13291
+ if (occupants.length > 1) return { kind: "blocked" };
13292
+ const target = occupants[0];
13293
+ if (target) return validateGridMove(layout, swapPositionUpdates(active, target));
13294
+ if (destination.row === active.row && destination.colStart === active.colStart && !isRepeaterGridLayoutInvalid(layout)) return { kind: "unchanged" };
13295
+ return validateGridMove(layout, [{
13296
+ id: active.id,
13297
+ row: destination.row,
13298
+ colStart: destination.colStart,
13299
+ span: active.span
13300
+ }]);
13301
+ }
13302
+ function buildRepeaterGridDropTargets(input) {
13303
+ const { layout, draggedId, rowsToRender } = input;
13304
+ const active = layout.byId[draggedId];
13305
+ if (!active || layout.placementPolicy === "flow" && isRepeaterGridLayoutInvalid(layout)) return [];
13306
+ if (layout.placementPolicy === "flow") return orderedGridItems(layout).map((item, index) => ({
13307
+ id: `repeater-grid-drop-${item.row}-${item.colStart}`,
13308
+ row: item.row,
13309
+ colStart: item.colStart,
13310
+ cell: item.startCell,
13311
+ decision: planRepeaterGridReorder(layout, draggedId, index, input.interaction ?? "drag")
13312
+ }));
13313
+ const rowCeiling = layout.maxRows ?? Math.max(1, rowsToRender ?? layout.maxRowUsed + 1, active.row + 1);
13314
+ const targets = Array.from({ length: rowCeiling * layout.columns }, (_, index) => {
13315
+ const row = Math.floor(index / layout.columns) + 1;
13316
+ const colStart = index % layout.columns + 1;
13317
+ return {
13318
+ id: `repeater-grid-drop-${row}-${colStart}`,
13319
+ row,
13320
+ colStart,
13321
+ cell: index + 1,
13322
+ decision: decideGridDrop(layout, active, {
13323
+ row,
13324
+ colStart
13325
+ })
13326
+ };
13327
+ });
13328
+ return targets.some((target) => target.row === active.row && target.colStart === active.colStart) ? targets : [...targets, {
13329
+ id: `repeater-grid-drop-${active.row}-${active.colStart}`,
13330
+ row: active.row,
13331
+ colStart: active.colStart,
13332
+ cell: active.startCell,
13333
+ decision: { kind: "unchanged" }
13334
+ }];
13335
+ }
13336
+ /** Width edits keep the item's start when it fits; otherwise relocate it to the first free cell.
13337
+ * Neighbours keep their positions. A full bounded grid blocks the change without partial writes.
13338
+ */
13339
+ function planRepeaterGridSpanChange(input) {
13340
+ const { layout, id, span } = input;
13341
+ const active = layout.byId[id];
13342
+ if (layout.placementPolicy === "flow" && isRepeaterGridLayoutInvalid(layout)) return { kind: "blocked" };
13343
+ if (!active || !isAcceptedRepeaterGridSpan(span, layout.columns, active.acceptedSpans)) return { kind: "blocked" };
13344
+ const nextSpan = Number(span);
13345
+ if (!isRepeaterGridLayoutInvalid(layout) && sourceGridItems(layout).find((item) => item.id === id)?.span === nextSpan) return { kind: "unchanged" };
13346
+ if (layout.placementPolicy === "flow") return planFlowItems(layout, orderedGridItems(layout).map((item) => item.id === id ? {
13347
+ ...item,
13348
+ span: nextSpan
13349
+ } : item));
13350
+ return planFreeGridSpanChange(layout, active, nextSpan);
13351
+ }
13352
+ /** Free width edits retain the current start when possible, then use the shared first-free planner. */
13353
+ function planFreeGridSpanChange(layout, active, nextSpan) {
13354
+ const id = active.id;
13355
+ const atCurrent = validateGridMove(layout, [{
13356
+ id,
13357
+ span: nextSpan,
13358
+ row: active.row,
13359
+ colStart: active.colStart
13360
+ }]);
13361
+ if (atCurrent.kind === "move") return atCurrent;
13362
+ const destination = findFirstAvailableRepeaterGridPlacement({
13363
+ items: layout.placements.filter((item) => item.id !== id),
13364
+ newItemSpan: nextSpan,
13365
+ acceptedSpans: active.acceptedSpans,
13366
+ columns: layout.columns,
13367
+ maxRows: layout.maxRows
13368
+ });
13369
+ return destination ? validateGridMove(layout, [{
13370
+ id,
13371
+ span: nextSpan,
13372
+ ...destination
13373
+ }]) : { kind: "blocked" };
13374
+ }
13375
+ /** Narrow rendering is a projection only. Never feed these placements into an authoring write. */
13376
+ function projectRepeaterGridLayout(layout, columns) {
13377
+ if (isRepeaterGridLayoutInvalid(layout)) return projectRepeaterGridLayoutForRendering(layout, columns);
13378
+ if (columns === layout.columns) return layout;
13379
+ if (layout.placementPolicy === "flow") return buildProjectedRepeaterGridLayout(layout, flowRepeaterGridItems(orderedGridItems(layout), columns), columns);
13380
+ const items = layout.placements.reduce((placed, item) => {
13381
+ const span = normalizeRepeaterGridSpan(item.span, columns);
13382
+ const destination = findFirstAvailableRepeaterGridPlacement({
13383
+ items: placed,
13384
+ newItemSpan: span,
13385
+ columns
13386
+ });
13387
+ return [...placed, {
13388
+ id: item.id,
13389
+ span,
13390
+ row: destination?.row ?? placed.length + 1,
13391
+ colStart: destination?.colStart ?? 1
13392
+ }];
13393
+ }, []);
13394
+ return buildProjectedRepeaterGridLayout(layout, items, columns);
13395
+ }
13396
+ function buildProjectedRepeaterGridLayout(source, items, columns) {
13397
+ const projected = buildRepeaterGridLayout({
13398
+ items: items.map(({ id, span, row, colStart, startNewRow }) => ({
13399
+ id,
13400
+ span,
13401
+ row,
13402
+ colStart,
13403
+ startNewRow
13404
+ })),
13405
+ columns,
13406
+ placementPolicy: source.placementPolicy === "flow" || isRepeaterGridLayoutInvalid(source) ? "flow" : source.placementPolicy
13407
+ });
13408
+ const placements = projected.placements.map((item) => ({
13409
+ ...item,
13410
+ sourceIndex: source.byId[item.id]?.sourceIndex ?? item.sourceIndex
13411
+ }));
13412
+ return {
13413
+ ...projected,
13414
+ placements,
13415
+ byId: Object.fromEntries(placements.map((item) => [item.id, item]))
13416
+ };
13417
+ }
13418
+ /** Public fallback: preserve saved array order, use non-dense auto-placement, and ignore authoring capacity. */
13419
+ function projectRepeaterGridLayoutForRendering(layout, columns = layout.columns) {
13420
+ if (!isRepeaterGridLayoutInvalid(layout)) {
13421
+ return projectRepeaterGridLayout(layout, columns);
13422
+ }
13423
+ const count = normalizeRepeaterGridColumns(columns);
13424
+ return buildProjectedRepeaterGridLayout(layout, flowRepeaterGridItems(orderedGridItems(layout), count), count);
13425
+ }
13426
+ /** An explicit column reduction re-places free grids through the existing placement primitive. */
13427
+ function planRepeaterGridColumnChange(layout, columns) {
13428
+ if (layout.placementPolicy === "flow" || columns >= layout.columns) return { kind: "unchanged" };
13429
+ if (isRepeaterGridLayoutInvalid(layout)) return { kind: "blocked" };
13430
+ const placed = orderedGridItems(layout).reduce((items, item) => {
13431
+ if (!items) return null;
13432
+ const bounded = normalizeRepeaterGridSpan(item.span, columns);
13433
+ const span = item.acceptedSpans ? Math.max(0, ...item.acceptedSpans.filter((width) => width <= bounded)) : bounded;
13434
+ const destination = findFirstAvailableRepeaterGridPlacement({
13435
+ items,
13436
+ columns,
13437
+ maxRows: layout.maxRows,
13438
+ newItemSpan: span,
13439
+ acceptedSpans: item.acceptedSpans
13440
+ });
13441
+ return destination ? [...items, {
13442
+ ...item,
13443
+ span,
13444
+ ...destination
13445
+ }] : null;
13446
+ }, []);
13447
+ const [first, ...rest] = placed ?? [];
13448
+ return first ? {
13449
+ kind: "move",
13450
+ updates: [first, ...rest]
13451
+ } : placed ? { kind: "unchanged" } : { kind: "blocked" };
13452
+ }
13453
+
13454
+ //#endregion
13455
+ //#region ../blocks/src/system/manifest/layoutGridConfiguration.ts
13456
+ const itemFieldsSchema = z.object({ fields: z.array(z.object({
13457
+ id: z.string(),
13458
+ type: z.string(),
13459
+ defaultValue: z.unknown().optional(),
13460
+ options: z.array(z.object({ value: z.string() })).optional(),
13461
+ multiple: z.boolean().optional(),
13462
+ source: z.object({ kind: z.string() }).optional()
13463
+ })) });
13464
+ function validateSpanSelect(span, columns, ctx) {
13465
+ if (span.type !== "select") return;
13466
+ const options = span.options ?? [];
13467
+ const invalidOptions = options.some((option) => !isAcceptedRepeaterGridSpan(option.value, columns) || String(Number(option.value)) !== option.value);
13468
+ if (span.multiple || options.length === 0 || invalidOptions || span.source && span.source.kind !== "static") {
13469
+ ctx.addIssue({
13470
+ code: "custom",
13471
+ path: ["ui", "layoutGridSpanField"],
13472
+ message: `Layout-grid span select must have single, static integer options within 1..${columns}`
13473
+ });
13474
+ }
13475
+ if (span.defaultValue !== undefined && !options.some((option) => option.value === span.defaultValue)) {
13476
+ ctx.addIssue({
13477
+ code: "custom",
13478
+ path: ["ui", "layoutGridSpanField"],
13479
+ message: "Layout-grid default span must be one of the span select options"
13480
+ });
13481
+ }
13482
+ }
13483
+ /** Both monomorphic and polymorphic item schemas have the same layout contract. */
13484
+ function validateLayoutGridConfiguration(data, ctx) {
13485
+ const ui = data.ui;
13486
+ if (ui?.repeaterPreset !== "layoutGrid") return;
13487
+ if (ui.layoutGridColumns !== undefined === (ui.layoutGridColumnsFields !== undefined)) {
13488
+ ctx.addIssue({
13489
+ code: "custom",
13490
+ path: ["ui", "layoutGridColumns"],
13491
+ message: "Layout-grid requires exactly one of ui.layoutGridColumns or ui.layoutGridColumnsFields.lg; do not set both"
13492
+ });
13493
+ }
13494
+ const spanId = resolveRepeaterGridSpanFieldId({ id: ui.layoutGridSpanField });
13495
+ const requiredIds = [
13496
+ spanId,
13497
+ resolveRepeaterGridRowFieldId({ id: ui.layoutGridRowField }),
13498
+ resolveRepeaterGridColumnFieldId({ id: ui.layoutGridColumnField })
13499
+ ];
13500
+ const schemas = data.polymorphic && data.itemTypes ? Object.values(data.itemTypes) : [data.schema];
13501
+ schemas.forEach((schema) => {
13502
+ const parsed = itemFieldsSchema.safeParse(schema);
13503
+ if (!parsed.success) return;
13504
+ if (!requiredIds.every((id) => parsed.data.fields.some((field) => field.id === id))) {
13505
+ ctx.addIssue({
13506
+ code: "custom",
13507
+ path: ["ui", "layoutGridRowField"],
13508
+ message: `layoutGrid repeaters require span/row/column fields (${requiredIds.join(", ")}) to exist in repeater item fields`
13509
+ });
13510
+ }
13511
+ if (ui.layoutGridStartNewRowField && !parsed.data.fields.some((field) => field.id === ui.layoutGridStartNewRowField && field.type === "boolean")) {
13512
+ ctx.addIssue({
13513
+ code: "custom",
13514
+ path: ["ui", "layoutGridStartNewRowField"],
13515
+ message: "Layout-grid Start a new row field must reference a boolean item field"
13516
+ });
13517
+ }
13518
+ if (ui.layoutGridStartNewRowField && ui.layoutGridColumns === undefined) {
13519
+ ctx.addIssue({
13520
+ code: "custom",
13521
+ path: ["ui", "layoutGridStartNewRowField"],
13522
+ message: "Layout-grid Start a new row requires fixed columns (flow placement)"
13523
+ });
13524
+ }
13525
+ parsed.data.fields.filter((field) => field.id === spanId).forEach((span) => validateSpanSelect(span, ui.layoutGridColumns ?? MAX_REPEATER_GRID_COLUMNS, ctx));
13526
+ });
13527
+ }
13528
+
13529
+ //#endregion
13530
+ //#region ../blocks/src/system/icons/presets.ts
13531
+ const ICON_PICKER_PRESETS = ["cta", "feature"];
13532
+ function isIconPickerPreset(value) {
13533
+ return typeof value === "string" && ICON_PICKER_PRESETS.includes(value);
13534
+ }
13535
+ function resolveIconPickerPreset(value) {
13536
+ return isIconPickerPreset(value) ? value : "cta";
13537
+ }
13538
+
13539
+ //#endregion
13540
+ //#region ../blocks/src/system/manifest/sdkBlockPresentationSchema.ts
13541
+ const sdkBlockPresentationShape = {
13542
+ variants: z.record(z.string().min(1), NodeSchema).optional(),
13543
+ defaultVariant: z.string().min(1).optional(),
13544
+ variantOptions: z.record(z.string().min(1), z.object({
13545
+ label: z.string().min(1).optional(),
13546
+ description: z.string().min(1).optional(),
13547
+ icon: z.string().min(1).optional()
13548
+ })).optional()
13549
+ };
13550
+ function validateSdkBlockDeclaration(data, ctx) {
13551
+ validateTitleSource(data, ctx);
13552
+ if (data.layout && data.variants) ctx.addIssue({
13553
+ code: z.ZodIssueCode.custom,
13554
+ message: "Custom block must define either `layout` or `variants`, not both",
13555
+ path: ["variants"]
13556
+ });
13557
+ if (data.variants) validateVariantKeys(data, ctx);
13558
+ }
13559
+ function validateTitleSource(data, ctx) {
13560
+ if (!data.titleSource) return;
13561
+ const sources = Array.isArray(data.titleSource) ? data.titleSource : [data.titleSource];
13562
+ sources.forEach((source, index) => {
13563
+ if (data.fields.some((field) => field !== null && typeof field === "object" && "id" in field && field.id === source)) return;
13564
+ ctx.addIssue({
13565
+ code: z.ZodIssueCode.custom,
13566
+ message: `titleSource must reference a valid field ID (got "${source}")`,
13567
+ path: Array.isArray(data.titleSource) ? ["titleSource", index] : ["titleSource"]
13568
+ });
13569
+ });
13570
+ }
13571
+ function validateVariantKeys(data, ctx) {
13572
+ const variants = data.variants ?? {};
13573
+ if (Object.keys(variants).length === 0) ctx.addIssue({
13574
+ code: z.ZodIssueCode.custom,
13575
+ message: "`variants` must contain at least one variant key",
13576
+ path: ["variants"]
13577
+ });
13578
+ if (data.defaultVariant && !Object.prototype.hasOwnProperty.call(variants, data.defaultVariant)) ctx.addIssue({
13579
+ code: z.ZodIssueCode.custom,
13580
+ message: "`defaultVariant` must be a key present in `variants`",
13581
+ path: ["defaultVariant"]
13582
+ });
13583
+ Object.keys(data.variantOptions ?? {}).filter((key) => !Object.prototype.hasOwnProperty.call(variants, key)).forEach((key) => ctx.addIssue({
13584
+ code: z.ZodIssueCode.custom,
13585
+ message: "`variantOptions` key must exist in `variants`",
13586
+ path: ["variantOptions", key]
13587
+ }));
13588
+ }
13589
+
13590
+ //#endregion
13591
+ //#region ../blocks/src/system/manifest/schema.ts
13592
+ const visibilityLevels = [
13593
+ "admin",
13594
+ "designer",
13595
+ "author"
13596
+ ];
13597
+ const MEDIA_KINDS = [
13598
+ "image",
13599
+ "video",
13600
+ "audio",
13601
+ "document",
13602
+ "spreadsheet",
13603
+ "archive"
13604
+ ];
13605
+ const DISPLAY_MEDIA_KINDS = [
13606
+ "image",
13607
+ "video",
13608
+ "audio"
13609
+ ];
13610
+ const DEFAULT_MEDIA_FIELD_KINDS = ["image"];
13611
+ const PUBLIC_FILE_FIELD_KINDS = [
13612
+ "document",
13613
+ "spreadsheet",
13614
+ "archive",
13615
+ "audio"
13616
+ ];
13617
+ function isDisplayMediaKind(kind) {
13618
+ return DISPLAY_MEDIA_KINDS.includes(kind);
13619
+ }
13620
+ const siteOptionSources = [
13621
+ "eventCategories",
13622
+ "eventSeries",
13623
+ "venues",
13624
+ "staffMembers",
13625
+ "appointmentServiceCategories"
13626
+ ];
13627
+ const mediaDeliverySchema = z.object({
13628
+ sizes: z.string().optional(),
13629
+ widthCandidates: z.array(z.number().int().positive()).optional(),
13630
+ maxCssWidth: z.number().positive().optional(),
13631
+ maxCssHeight: z.number().positive().optional(),
13632
+ strategy: z.enum([
13633
+ "default",
13634
+ "columnsAuto",
13635
+ "imageGalleryVariant"
13636
+ ]).optional(),
13637
+ strategyOptions: z.record(z.string(), z.unknown()).optional()
13638
+ });
13639
+ const selectOptionSchema = z.object({
13640
+ value: z.string(),
13641
+ label: z.string(),
13642
+ description: z.string().optional(),
13643
+ icon: z.string().optional()
13644
+ });
13645
+ const selectSourceSchema = z.discriminatedUnion("kind", [
13646
+ z.object({ kind: z.literal("static") }),
13647
+ z.object({
13648
+ kind: z.literal("site"),
13649
+ optionSource: z.enum(siteOptionSources)
13650
+ }),
13651
+ z.object({ kind: z.literal("sdk") })
13652
+ ]);
13653
+ function assertNeverSelectSource(value) {
13654
+ throw new Error(`Unhandled select source: ${JSON.stringify(value)}`);
13655
+ }
13656
+ const visibleWhenPredicateSchema = z.object({
13657
+ field: z.string().min(1),
13658
+ equals: z.any().optional(),
13659
+ notEquals: z.any().optional(),
13660
+ oneOf: z.array(z.any()).optional(),
13661
+ notIn: z.array(z.any()).optional(),
13662
+ minItems: z.number().int().nonnegative().optional(),
13663
+ maxItems: z.number().int().nonnegative().optional()
13664
+ });
13665
+ const visibleWhenSchema = z.lazy(() => z.union([
13666
+ visibleWhenPredicateSchema,
13667
+ z.object({ allOf: z.array(visibleWhenSchema).min(1) }),
13668
+ z.object({ anyOf: z.array(visibleWhenSchema).min(1) })
13669
+ ]));
13670
+ const uiSchema = z.object({
13671
+ widget: z.string().optional(),
13672
+ preset: z.enum(["disclosure"]).optional(),
13673
+ flattenInRepeater: z.boolean().optional(),
13674
+ hidden: z.boolean().optional(),
13675
+ hideLabel: z.boolean().optional(),
13676
+ hideDescription: z.boolean().optional(),
13677
+ showCharCount: z.union([z.number().int().positive(), z.object({ max: z.number().int().positive() })]).optional(),
13678
+ showSlugPreview: z.boolean().optional(),
13679
+ variant: z.enum([
13680
+ "full",
13681
+ "inline",
13682
+ "limited",
13683
+ "media",
13684
+ "notes"
13685
+ ]).optional(),
13686
+ richTextVariant: z.enum([
13687
+ "full",
13688
+ "inline",
13689
+ "limited",
13690
+ "media",
13691
+ "notes"
13692
+ ]).optional(),
13693
+ cropEditorMode: z.enum(["dialog", "inline"]).optional(),
13694
+ cropEditorAutoOpen: z.boolean().optional(),
13695
+ mediaPickerAutoOpen: z.boolean().optional(),
13696
+ showMediaPreview: z.boolean().optional(),
13697
+ showMediaOverlayActions: z.boolean().optional(),
13698
+ mediaPreviewMaxWidth: z.enum([
13699
+ "xs",
13700
+ "sm",
13701
+ "md",
13702
+ "none"
13703
+ ]).optional(),
13704
+ preventMediaPreviewUpscale: z.boolean().optional(),
13705
+ mediaDelivery: mediaDeliverySchema.optional(),
13706
+ mediaDeliveryByVariant: z.record(z.string(), mediaDeliverySchema).optional(),
13707
+ inputType: z.enum([
13708
+ "text",
13709
+ "email",
13710
+ "tel",
13711
+ "number"
13712
+ ]).optional(),
13713
+ min: z.number().optional(),
13714
+ max: z.number().optional(),
13715
+ step: z.number().optional(),
13716
+ showValue: z.boolean().optional(),
13717
+ valueUnit: z.enum([
13718
+ "percent",
13719
+ "pixels",
13720
+ "unitless"
13721
+ ]).optional(),
13722
+ unsetValue: z.enum(["max", "field-default"]).optional(),
13723
+ allowDirectInput: z.boolean().optional(),
13724
+ pattern: z.string().optional(),
13725
+ placeholder: z.string().optional(),
13726
+ visibleWhen: visibleWhenSchema.optional(),
13727
+ modalConfig: z.object({
13728
+ buttonLabel: z.string().optional(),
13729
+ description: z.string().optional(),
13730
+ buttonVariant: z.enum(["default", "outline"]).optional(),
13731
+ showCustomizedIndicator: z.boolean().optional(),
13732
+ maxWidth: z.string().optional()
13733
+ }).optional(),
13734
+ allowedTypes: z.array(z.enum([
13735
+ "color",
13736
+ "gradient",
13737
+ "image"
13738
+ ])).optional(),
13739
+ fullWidth: z.boolean().optional(),
13740
+ row: z.string().optional(),
13741
+ colSpan: z.number().int().min(1).max(4).optional(),
13742
+ layout: z.enum(["stack", "grid"]).optional(),
13743
+ columns: z.number().int().min(2).max(4).optional(),
13744
+ gridColsPreset: z.enum([
13745
+ "simple2",
13746
+ "simple3",
13747
+ "simple4",
13748
+ "buttonCtaRow"
13749
+ ]).optional(),
13750
+ gridColumns: z.object({
13751
+ base: z.number().int().min(1).max(4).optional(),
13752
+ sm: z.number().int().min(1).max(4).optional(),
13753
+ md: z.number().int().min(1).max(4).optional(),
13754
+ lg: z.number().int().min(1).max(4).optional(),
13755
+ xl: z.number().int().min(1).max(4).optional()
13756
+ }).optional(),
13757
+ gridColsClassName: z.string().optional(),
13758
+ repeaterPreset: z.enum(["thumbnailGrid", "layoutGrid"]).optional(),
13759
+ thumbnailField: z.string().optional(),
13760
+ thumbnailAltField: z.string().optional(),
13761
+ thumbnailLabelField: z.string().optional(),
13762
+ thumbnailAspectRatio: z.string().optional(),
13763
+ thumbnailCardDensity: z.enum(["compact", "comfortable"]).optional(),
13764
+ layoutGridColumns: z.union([
13765
+ z.literal(1),
13766
+ z.literal(2),
13767
+ z.literal(3),
13768
+ z.literal(4),
13769
+ z.literal(5),
13770
+ z.literal(6)
13771
+ ]).optional(),
13772
+ layoutGridColumnsFields: z.object({
13773
+ base: z.string().optional(),
13774
+ sm: z.string().optional(),
13775
+ md: z.string().optional(),
13776
+ lg: z.string().min(1),
13777
+ xl: z.string().optional()
13778
+ }).optional(),
13779
+ layoutGridSpanField: z.string().optional(),
13780
+ layoutGridStartNewRowField: z.string().optional(),
13781
+ layoutGridRowField: z.string().optional(),
13782
+ layoutGridColumnField: z.string().optional(),
13783
+ layoutGridMaxRows: z.number().int().positive().optional(),
13784
+ iconPickerPreset: z.enum(ICON_PICKER_PRESETS).optional(),
13785
+ contentTypeField: z.string().optional(),
13786
+ fixedContentType: z.string().optional(),
13787
+ valueFrom: z.enum([
13788
+ "id",
13789
+ "identifier",
13790
+ "slug"
13791
+ ]).optional(),
13792
+ siteOptionSource: z.enum(siteOptionSources).optional(),
13793
+ searchThreshold: z.number().int().positive().optional(),
13794
+ renderInHeader: z.boolean().optional(),
13795
+ intent: z.enum([
13796
+ "content",
13797
+ "display",
13798
+ "behavior",
13799
+ "style"
13800
+ ]).optional(),
13801
+ underLooks: z.literal("owner").optional(),
13802
+ valueContract: z.enum(["boxBackground", "location"]).optional()
13803
+ }).partial().superRefine((ui, ctx) => {
13804
+ if (ui.layoutGridColumns !== undefined && ui.layoutGridColumnsFields !== undefined) {
13805
+ ctx.addIssue({
13806
+ code: "custom",
13807
+ path: ["layoutGridColumns"],
13808
+ message: "Layout-grid requires exactly one of ui.layoutGridColumns or ui.layoutGridColumnsFields.lg; do not set both"
13809
+ });
13810
+ }
13811
+ }).transform((input) => {
13812
+ const { layoutGridColumns, layoutGridColumnsFields, ...ui } = input;
13813
+ if (layoutGridColumns !== undefined) return {
13814
+ ...ui,
13815
+ layoutGridColumns
13816
+ };
13817
+ if (layoutGridColumnsFields !== undefined) return {
13818
+ ...ui,
13819
+ layoutGridColumnsFields
13820
+ };
13821
+ return ui;
13822
+ });
13823
+ const baseFieldSchema = z.object({
13824
+ id: z.string().min(1, "Field id is required"),
13825
+ label: z.string().min(1, "Field label is required"),
13826
+ description: z.string().optional(),
13827
+ visibleRoles: z.array(z.enum(visibilityLevels)).optional(),
13828
+ defaultValue: z.any().optional(),
13829
+ required: z.boolean().default(false),
13830
+ ui: uiSchema.optional()
13831
+ });
13832
+ const textFieldSchema = baseFieldSchema.extend({
13833
+ type: z.literal("text"),
13834
+ multiline: z.boolean().default(false),
13835
+ maxLength: z.number().int().positive().optional()
13836
+ });
13837
+ const richTextFieldSchema = baseFieldSchema.extend({ type: z.literal("richText") });
13838
+ const inlineTextFieldSchema = baseFieldSchema.extend({
13839
+ type: z.literal("inlineText"),
13840
+ maxLength: z.number().int().positive().optional(),
13841
+ plainTextField: z.string().min(1).optional()
13842
+ });
13843
+ function hasMediaDefaultIdentifier(value) {
13844
+ return value !== null && typeof value === "object" && !Array.isArray(value) && typeof value.identifier === "string" && value.identifier.trim().length > 0;
13845
+ }
13846
+ function validateMediaFieldDefault(field, ctx) {
13847
+ if (field.defaultValue !== undefined && field.defaultValue !== null && !hasMediaDefaultIdentifier(field.defaultValue)) {
13848
+ ctx.addIssue({
13849
+ code: z.ZodIssueCode.custom,
13850
+ path: ["defaultValue"],
13851
+ message: `${field.type} defaultValue must be null or an object with a non-empty identifier`
13852
+ });
13853
+ }
13854
+ }
13855
+ const mediaFieldSchema = baseFieldSchema.extend({
13856
+ type: z.literal("media"),
13857
+ mediaKinds: z.array(z.enum(MEDIA_KINDS)).default([...DEFAULT_MEDIA_FIELD_KINDS]).transform((kinds) => {
13858
+ const displayKinds = kinds.filter(isDisplayMediaKind);
13859
+ return displayKinds.length > 0 ? displayKinds : [...DEFAULT_MEDIA_FIELD_KINDS];
13860
+ }),
13861
+ aspectRatio: z.string().optional()
13862
+ }).superRefine(validateMediaFieldDefault);
13863
+ const fileFieldSchema = baseFieldSchema.extend({
13864
+ type: z.literal("file"),
13865
+ fileKinds: z.array(z.enum(PUBLIC_FILE_FIELD_KINDS)).default([...PUBLIC_FILE_FIELD_KINDS])
13866
+ }).superRefine(validateMediaFieldDefault);
13867
+ const booleanFieldSchema = baseFieldSchema.extend({ type: z.literal("boolean") });
13868
+ const dateFieldSchema = baseFieldSchema.extend({ type: z.literal("date") });
13869
+ const timeFieldSchema = baseFieldSchema.extend({ type: z.literal("time") });
13870
+ const dateTimeFieldSchema = baseFieldSchema.extend({ type: z.literal("datetime") });
13871
+ const slugFieldSchema = baseFieldSchema.extend({
13872
+ type: z.literal("slug"),
13873
+ sourceFieldId: z.string().min(1).optional(),
13874
+ statusFieldId: z.string().min(1).optional(),
13875
+ maxLength: z.number().int().positive().optional()
13876
+ });
13877
+ const urlFieldSchema = baseFieldSchema.extend({
13878
+ type: z.literal("url"),
13879
+ allowRelative: z.boolean().default(false)
13880
+ });
13881
+ const linkFieldSchema = baseFieldSchema.extend({ type: z.literal("link") });
13882
+ const selectFieldSchema = baseFieldSchema.extend({
13883
+ type: z.literal("select"),
13884
+ options: z.array(selectOptionSchema),
13885
+ multiple: z.boolean().default(false),
13886
+ source: selectSourceSchema.optional()
13887
+ }).superRefine((field, ctx) => {
13888
+ const widget = field.ui?.widget;
13889
+ const siteOptionSource = field.ui?.siteOptionSource;
13890
+ if (!field.source) {
13891
+ if (siteOptionSource || widget === "sdkSelect") {
13892
+ ctx.addIssue({
13893
+ code: z.ZodIssueCode.custom,
13894
+ message: "Runtime-backed select fields must declare source.kind explicitly",
13895
+ path: ["source"]
13896
+ });
13897
+ return;
13898
+ }
13899
+ if (field.options.length === 0) {
13900
+ ctx.addIssue({
13901
+ code: z.ZodIssueCode.custom,
13902
+ message: "Select fields require at least one option unless a runtime source is configured",
13903
+ path: ["options"]
13904
+ });
13905
+ }
13906
+ return;
13907
+ }
13908
+ switch (field.source.kind) {
13909
+ case "static":
13910
+ if (siteOptionSource || widget === "sdkSelect") {
13911
+ ctx.addIssue({
13912
+ code: z.ZodIssueCode.custom,
13913
+ message: "Static select fields cannot declare runtime-backed UI metadata",
13914
+ path: ["ui"]
13915
+ });
13916
+ }
13917
+ if (field.options.length === 0) {
13918
+ ctx.addIssue({
13919
+ code: z.ZodIssueCode.custom,
13920
+ message: "Static select fields require at least one option",
13921
+ path: ["options"]
13922
+ });
13923
+ }
13924
+ return;
13925
+ case "site":
13926
+ if (field.options.length > 0) {
13927
+ ctx.addIssue({
13928
+ code: z.ZodIssueCode.custom,
13929
+ message: "Site-backed select fields must not define static options",
13930
+ path: ["options"]
13931
+ });
13932
+ }
13933
+ if (widget !== "siteOptionPicker") {
13934
+ ctx.addIssue({
13935
+ code: z.ZodIssueCode.custom,
13936
+ message: "Site-backed select fields must use the siteOptionPicker widget",
13937
+ path: ["ui", "widget"]
13938
+ });
13939
+ }
13940
+ if (siteOptionSource !== field.source.optionSource) {
13941
+ ctx.addIssue({
13942
+ code: z.ZodIssueCode.custom,
13943
+ message: "Site-backed select fields must keep ui.siteOptionSource in sync with source.optionSource",
13944
+ path: ["ui", "siteOptionSource"]
13945
+ });
13946
+ }
13947
+ return;
13948
+ case "sdk":
13949
+ if (widget !== "sdkSelect") {
13950
+ ctx.addIssue({
13951
+ code: z.ZodIssueCode.custom,
13952
+ message: "SDK-backed select fields must use the sdkSelect widget",
13953
+ path: ["ui", "widget"]
13954
+ });
13955
+ }
13956
+ if (siteOptionSource) {
13957
+ ctx.addIssue({
13958
+ code: z.ZodIssueCode.custom,
13959
+ message: "SDK-backed select fields cannot declare ui.siteOptionSource",
13960
+ path: ["ui", "siteOptionSource"]
13961
+ });
13962
+ }
13963
+ return;
13964
+ default: return assertNeverSelectSource(field.source);
13965
+ }
13966
+ });
13967
+ const referenceFieldSchema = baseFieldSchema.extend({
13968
+ type: z.literal("reference"),
13969
+ referenceKind: z.string().min(1),
13970
+ allowManualEntry: z.boolean().default(false)
13971
+ });
13972
+ /**
13973
+ * Item type schema for polymorphic repeaters.
13974
+ * Each type has a label, optional icon, and its own field schema.
13975
+ */
13976
+ const itemTypeSchema = z.object({
13977
+ label: z.string().min(1),
13978
+ icon: z.string().optional(),
13979
+ fields: z.lazy(() => getFieldSchemaInternal().array().min(1, "Item type requires at least one field"))
13980
+ });
13981
+ /**
13982
+ * Repeater fields allow nesting of other fields within them.
13983
+ * This enables complex layouts and structures by repeating a set of fields.
13984
+ *
13985
+ * Supports two modes:
13986
+ * 1. Monomorphic (default): All items have the same schema
13987
+ * 2. Polymorphic: Items can be different types (e.g., card, quote, stat)
13988
+ *
13989
+ * Uses z.lazy() to allow mutual recursion.
13990
+ */
13991
+ const repeaterFieldSchema = baseFieldSchema.extend({
13992
+ type: z.literal("repeater"),
13993
+ itemLabel: z.string().default("Item"),
13994
+ itemLabelSource: z.string().optional(),
13995
+ showSummary: z.boolean().optional(),
13996
+ summaryFields: z.string().array().optional(),
13997
+ summaryMaxChars: z.number().int().positive().optional(),
13998
+ minItems: z.number().int().min(0).default(0),
13999
+ maxItems: z.number().int().positive().optional(),
14000
+ schema: z.object({ fields: z.lazy(() => getFieldSchemaInternal().array().min(1, "Repeater requires at least one field")) }).optional(),
14001
+ polymorphic: z.boolean().optional(),
14002
+ itemTypes: z.record(z.string(), itemTypeSchema).optional(),
14003
+ allowConversion: z.boolean().default(true)
14004
+ }).refine((data) => {
14005
+ const hasSchema = data.schema !== undefined;
14006
+ const hasPolymorphic = data.polymorphic === true && data.itemTypes !== undefined;
14007
+ return hasSchema !== hasPolymorphic;
14008
+ }, { message: "Repeater must have either 'schema' (monomorphic) or 'polymorphic: true' with 'itemTypes' (polymorphic)" }).superRefine(validateLayoutGridConfiguration).refine((data) => {
14009
+ const thumbnailAspectRatio = data.ui?.thumbnailAspectRatio;
14010
+ if (!thumbnailAspectRatio) {
14011
+ return true;
14012
+ }
14013
+ return parseAspectRatioValue(thumbnailAspectRatio) != null;
14014
+ }, {
14015
+ message: "ui.thumbnailAspectRatio must be a valid ratio (e.g. '16/9', '4:3', or '1.5')",
14016
+ path: ["ui", "thumbnailAspectRatio"]
14017
+ }).refine((data) => {
14018
+ const labelField = data.ui?.thumbnailLabelField;
14019
+ if (!labelField) {
14020
+ return true;
14021
+ }
14022
+ const schemaFields = data.schema?.fields;
14023
+ if (!schemaFields) {
14024
+ return true;
14025
+ }
14026
+ const field = schemaFields.find((candidate) => candidate.id === labelField);
14027
+ return field?.type === "text";
14028
+ }, {
14029
+ message: "ui.thumbnailLabelField must reference a text field in the repeater schema",
14030
+ path: ["ui", "thumbnailLabelField"]
14031
+ });
14032
+ const groupFieldSchema = baseFieldSchema.extend({
14033
+ type: z.literal("group"),
14034
+ schema: z.object({ fields: z.lazy(() => getFieldSchemaInternal().array().min(1, "Group requires at least one field")) })
14035
+ });
14036
+ const modalFieldSchema = baseFieldSchema.extend({
14037
+ type: z.literal("modal"),
14038
+ schema: z.object({ fields: z.lazy(() => getFieldSchemaInternal().array().min(1, "Modal requires at least one field")) })
14039
+ });
14040
+ const numberFieldSchema = baseFieldSchema.extend({
14041
+ type: z.literal("number"),
14042
+ min: z.number().optional(),
14043
+ max: z.number().optional(),
14044
+ step: z.number().optional()
14045
+ });
14046
+ const tabDefinitionSchema = z.object({
14047
+ id: z.string().min(1),
14048
+ label: z.string().min(1),
14049
+ icon: z.string().optional(),
14050
+ description: z.string().optional(),
14051
+ fields: z.lazy(() => getFieldSchemaInternal().array()),
14052
+ sdkSectionOption: z.enum([
14053
+ "backgroundColor",
14054
+ "backgroundGradient",
14055
+ "backgroundImage"
14056
+ ]).optional()
14057
+ });
14058
+ const tabGroupFieldSchema = baseFieldSchema.extend({
14059
+ type: z.literal("tabGroup"),
14060
+ tabs: z.array(tabDefinitionSchema).min(1, "TabGroup requires at least one tab"),
14061
+ activeTabField: z.string().optional()
14062
+ });
14063
+ const presetOptionSchema = z.object({
14064
+ value: z.string(),
14065
+ label: z.string()
14066
+ });
14067
+ const presetOrCustomFieldSchema = baseFieldSchema.extend({
14068
+ type: z.literal("presetOrCustom"),
14069
+ presets: z.array(presetOptionSchema).min(1, "PresetOrCustom requires at least one preset"),
14070
+ customInput: z.object({
14071
+ placeholder: z.string().optional(),
14072
+ pattern: z.string().optional(),
14073
+ helpText: z.string().optional()
14074
+ }).optional()
14075
+ });
14076
+ const contentTypeSelectFieldSchema = baseFieldSchema.extend({
14077
+ type: z.literal("contentTypeSelect"),
14078
+ filter: z.enum([
14079
+ "all",
14080
+ "routable",
14081
+ "nonRoutable"
14082
+ ]).default("all")
14083
+ });
14084
+ const entryPickerFieldSchema = baseFieldSchema.extend({
14085
+ type: z.literal("entryPicker"),
14086
+ fixedContentType: z.string().optional(),
14087
+ contentTypeField: z.string().optional(),
14088
+ valueFrom: z.enum([
14089
+ "id",
14090
+ "identifier",
14091
+ "slug"
14092
+ ]).optional()
14093
+ });
14094
+ let _fieldSchemaInternal = null;
14095
+ function getFieldSchemaInternal() {
14096
+ if (_fieldSchemaInternal) {
14097
+ return _fieldSchemaInternal;
14098
+ }
14099
+ _fieldSchemaInternal = z.discriminatedUnion("type", [
14100
+ textFieldSchema,
14101
+ richTextFieldSchema,
14102
+ inlineTextFieldSchema,
14103
+ mediaFieldSchema,
14104
+ fileFieldSchema,
14105
+ booleanFieldSchema,
14106
+ numberFieldSchema,
14107
+ dateFieldSchema,
14108
+ timeFieldSchema,
14109
+ dateTimeFieldSchema,
14110
+ slugFieldSchema,
14111
+ urlFieldSchema,
14112
+ linkFieldSchema,
14113
+ selectFieldSchema,
14114
+ referenceFieldSchema,
14115
+ repeaterFieldSchema,
14116
+ groupFieldSchema,
14117
+ modalFieldSchema,
14118
+ tabGroupFieldSchema,
14119
+ presetOrCustomFieldSchema,
14120
+ contentTypeSelectFieldSchema,
14121
+ entryPickerFieldSchema
14122
+ ]);
14123
+ return _fieldSchemaInternal;
14124
+ }
14125
+ function getFieldSchema() {
14126
+ return getFieldSchemaInternal();
14127
+ }
14128
+ const fieldSchema = new Proxy({}, {
14129
+ get(_, prop) {
14130
+ const schema = getFieldSchemaInternal();
14131
+ const value = schema[prop];
14132
+ return typeof value === "function" ? value.bind(schema) : value;
14133
+ },
14134
+ has(_, prop) {
14135
+ return prop in getFieldSchemaInternal();
14136
+ }
14137
+ });
14138
+ function parseFieldDefinition(input) {
14139
+ return fieldSchema.parse(input);
14140
+ }
14141
+ function parseFieldDefinitions(input) {
14142
+ return fieldSchema.array().parse(input);
14143
+ }
14144
+ /**
14145
+ * Normalize legacy select fields into the closed source union before any consumer
14146
+ * branches on source semantics. Runtime-backed definitions must be explicit;
14147
+ * legacy static definitions normalize to `kind: "static"`.
14148
+ */
14149
+ function normalizeSelectSource(field) {
14150
+ return field.source ?? { kind: "static" };
14151
+ }
14152
+ function expectParsedSelectField(field) {
14153
+ if (field.type !== "select") {
14154
+ throw new Error(`Expected select field, received ${field.type}`);
14155
+ }
14156
+ return field;
14157
+ }
14158
+ function createStaticSelectField(input) {
14159
+ return expectParsedSelectField(parseFieldDefinition({
14160
+ ...input,
14161
+ type: "select",
14162
+ options: input.options,
14163
+ multiple: input.multiple ?? false,
14164
+ source: { kind: "static" }
14165
+ }));
14166
+ }
14167
+ function createSiteOptionSelectField(input) {
14168
+ return expectParsedSelectField(parseFieldDefinition({
14169
+ ...input,
14170
+ type: "select",
14171
+ options: [],
14172
+ multiple: input.multiple ?? false,
14173
+ source: {
14174
+ kind: "site",
14175
+ optionSource: input.optionSource
14176
+ },
14177
+ ui: {
14178
+ ...input.ui ?? {},
14179
+ widget: "siteOptionPicker",
14180
+ siteOptionSource: input.optionSource
14181
+ }
14182
+ }));
14183
+ }
14184
+ function createSdkSelectField(input) {
14185
+ return expectParsedSelectField(parseFieldDefinition({
14186
+ ...input,
14187
+ type: "select",
14188
+ options: input.options,
14189
+ multiple: input.multiple ?? false,
14190
+ source: { kind: "sdk" },
14191
+ ui: {
14192
+ ...input.ui ?? {},
14193
+ widget: "sdkSelect"
14194
+ }
14195
+ }));
14196
+ }
14197
+ const slotSchema = z.object({
14198
+ id: z.string().min(1),
14199
+ label: z.string().min(1),
14200
+ allowedKinds: z.array(z.string()).default([]),
14201
+ min: z.number().int().min(0).default(0),
14202
+ max: z.number().int().positive().optional(),
14203
+ help: z.string().optional(),
14204
+ visibleRoles: z.array(z.enum(visibilityLevels)).optional()
14205
+ });
14206
+ const typographyTokens = [
14207
+ "display",
14208
+ "heading",
14209
+ "subheading",
14210
+ "body",
14211
+ "caption"
14212
+ ];
14213
+ const colorTokens = [
14214
+ "background",
14215
+ "surface",
14216
+ "foreground",
14217
+ "accent",
14218
+ "muted"
14219
+ ];
14220
+ const spacingTokens = [
14221
+ "none",
14222
+ "xs",
14223
+ "sm",
14224
+ "md",
14225
+ "lg",
14226
+ "xl"
14227
+ ];
14228
+ const radiusTokens = [
14229
+ "none",
14230
+ "sm",
14231
+ "md",
14232
+ "lg",
14233
+ "full"
14234
+ ];
14235
+ const styleTokenSchema = z.object({
14236
+ background: z.enum(colorTokens).optional(),
14237
+ foreground: z.enum(colorTokens).optional(),
14238
+ border: z.enum(colorTokens).optional(),
14239
+ typography: z.enum(typographyTokens).optional(),
14240
+ spacing: z.enum(spacingTokens).optional(),
14241
+ radius: z.enum(radiusTokens).optional()
14242
+ });
14243
+ const behaviourSchema = z.object({
14244
+ supportsThemeSwitching: z.boolean().default(true),
14245
+ inlineEditing: z.boolean().default(false),
14246
+ animation: z.boolean().default(false),
14247
+ paletteHidden: z.boolean().default(false),
14248
+ managesOwnWrapper: z.boolean().optional()
14249
+ });
14250
+ const builtInBlockKindsSchema = z.array(z.enum(SYSTEM_BLOCK_KINDS)).readonly();
14251
+ const blockRequiredFeatureSchema = z.enum([
14252
+ "bookings.appointments",
14253
+ "bookings.events",
14254
+ "shop",
14255
+ "newsletter",
14256
+ "analytics"
14257
+ ]);
14258
+ const blockCategoryEnum = z.enum([
14259
+ "text-and-images",
14260
+ "photos",
14261
+ "people",
14262
+ "events-and-bookings",
14263
+ "shop",
14264
+ "forms-and-sign-ups",
14265
+ "marketing",
14266
+ "content",
14267
+ "blog",
14268
+ "media",
14269
+ "layout",
14270
+ "interactive"
14271
+ ]);
14272
+ /**
14273
+ * Explicit authoring classification of where a block's rendered content comes from.
14274
+ *
14275
+ * - `inline` — all content is authored inline in the block's own fields.
14276
+ * - `entries` — the block renders content-type entries; `contentType` names the backing type.
14277
+ * - `mixed` — the block has inline fields AND renders entries; `contentType` names the
14278
+ * backing type when a single one applies.
14279
+ *
14280
+ * This is EXPLICIT metadata declared on every block definition (not inferred from
14281
+ * dataLoaders/field types), so the catalog can classify entry-backed vs inline blocks
14282
+ * honestly. A registry-wide test asserts every registered block declares it.
14283
+ */
14284
+ const blockContentSourceKinds = [
14285
+ "inline",
14286
+ "entries",
14287
+ "mixed"
14288
+ ];
14289
+ const blockContentSourceSchema = z.object({
14290
+ kind: z.enum(blockContentSourceKinds),
14291
+ contentType: z.string().min(1).optional()
14292
+ }).superRefine((value, ctx) => {
14293
+ if (value.kind === "inline" && value.contentType) {
14294
+ ctx.addIssue({
14295
+ code: z.ZodIssueCode.custom,
14296
+ path: ["contentType"],
14297
+ message: "contentType is not allowed when contentSource.kind is \"inline\""
14298
+ });
14299
+ }
14300
+ });
14301
+ const manifestSchema = z.object({
14302
+ name: z.string().min(1),
14303
+ version: z.string().min(1),
14304
+ title: z.string().min(1),
14305
+ titleSource: z.union([z.string().min(1), z.array(z.string().min(1)).min(1)]).optional(),
14306
+ description: z.string().optional(),
14307
+ component: z.string().min(1),
14308
+ fields: fieldSchema.array().default([]),
14309
+ slots: slotSchema.array().default([]),
14310
+ styleTokens: styleTokenSchema.optional(),
14311
+ behaviours: behaviourSchema.optional(),
14312
+ layout: NodeTreeSchema.optional(),
14313
+ lookField: z.string().min(1).optional(),
14314
+ sourceLine: z.object({
14315
+ text: z.string(),
14316
+ target: z.enum([
14317
+ "testimonials",
14318
+ "team",
14319
+ "mailing-lists"
14320
+ ]),
14321
+ when: z.object({
14322
+ field: z.string(),
14323
+ equals: z.string()
14324
+ }).optional()
14325
+ }).optional(),
14326
+ promotedFields: z.array(z.string().min(1)).optional(),
14327
+ variantMeaning: z.discriminatedUnion("kind", [z.object({
14328
+ kind: z.literal("arrangement"),
14329
+ lookOnlyKeys: z.array(z.string().min(1)).readonly().optional()
14330
+ }), z.object({
14331
+ kind: z.literal("mode"),
14332
+ modes: z.array(z.enum(arrangementModes)).min(1).readonly(),
14333
+ defaultMode: z.enum(arrangementModes),
14334
+ arrangementField: z.string().min(1),
14335
+ keys: z.array(z.string().min(1)).min(1).readonly(),
14336
+ defaultKey: z.string().min(1),
14337
+ lookOnlyKeys: z.array(z.string().min(1)).readonly().optional(),
14338
+ modeLabels: z.record(z.string(), z.string().min(1)).readonly().optional()
14339
+ })]),
14340
+ variants: z.record(z.string(), NodeTreeSchema).optional(),
14341
+ defaultVariant: z.string().optional(),
14342
+ variantOptions: z.record(z.string(), z.object({
14343
+ label: z.string().optional(),
14344
+ description: z.string().optional(),
14345
+ icon: z.string().optional()
14346
+ }).optional()).optional(),
14347
+ category: blockCategoryEnum.optional(),
14348
+ contentTypes: z.array(z.string()).optional(),
14349
+ requiredFeatures: z.array(blockRequiredFeatureSchema).optional(),
14350
+ contentSource: blockContentSourceSchema.optional(),
14351
+ tags: z.array(z.string()).optional(),
14352
+ icon: z.string().optional()
14353
+ });
14354
+ /** SDK Looks name a single top-level select owned by this definition. */
14355
+ function validateManifestLookField(data, ctx) {
14356
+ if (!data.lookField) return;
14357
+ const valid = data.fields.some((field) => field !== null && typeof field === "object" && "id" in field && field.id === data.lookField && "type" in field && field.type === "select" && (!("multiple" in field) || field.multiple !== true));
14358
+ if (!valid) ctx.addIssue({
14359
+ code: z.ZodIssueCode.custom,
14360
+ path: ["lookField"],
14361
+ message: "lookField must name an existing single select field"
14362
+ });
14363
+ }
14364
+ const blockSectionDeclarationShape = {
14365
+ promotedFields: z.array(z.string().min(1)).optional(),
14366
+ lookField: z.string().min(1).optional()
14367
+ };
14368
+
14369
+ //#endregion
14370
+ //#region ../api/src/sdkConfig/contracts.ts
14371
+ /**
14372
+ * Canonical SDK config Zod contracts.
14373
+ *
14374
+ * Goal: a single source-of-truth for request payload shapes used across dashboard + SDK.
14375
+ * - Endpoint request/response types live in `packages/api/src/types.ts`
14376
+ * - Schemas live here and are tied to those types via `satisfies z.core.$ZodType<...>`
14377
+ *
14378
+ * NOTE: These schemas represent the *wire/input* shape for API requests.
14379
+ * Service-layer code should parse/normalize again (defaults/refinements) into domain types.
14380
+ */
14381
+ /**
14382
+ * UNSAFE: Retype an existing Zod schema to a different (compile-time) output type.
14383
+ *
14384
+ * Use only for cases where:
14385
+ * - the runtime schema is known to accept the desired *input/wire* shape, but
14386
+ * - Zod's inferred output type doesn't match the desired TS type due to transforms/defaults.
14387
+ *
14388
+ * If used incorrectly, this can lie to TypeScript and cause drift.
14389
+ *
14390
+ * @internal
14391
+ */
14392
+ function unsafeRetypeZodSchema(schema) {
14393
+ return schema;
14394
+ }
14395
+ const navIdListSchema = z.preprocess((value) => {
14396
+ if (!Array.isArray(value)) return undefined;
14397
+ const filtered = value.filter((v) => typeof v === "string" && v.trim().length > 0);
14398
+ return filtered.length > 0 ? filtered : undefined;
14399
+ }, z.array(z.string()).max(200).optional());
14400
+ const dashboardNavVisibilityBaseSchema = z.object({
14401
+ mode: z.enum(["augment", "restrict"]).optional(),
14402
+ allowIds: navIdListSchema,
14403
+ denyIds: navIdListSchema
14404
+ }).strip();
14405
+ const dashboardNavVisibilityRolesSchema = z.object({
14406
+ administrator: dashboardNavVisibilityBaseSchema.optional(),
14407
+ editor: dashboardNavVisibilityBaseSchema.optional(),
14408
+ team_member: dashboardNavVisibilityBaseSchema.optional()
14409
+ }).strip().optional();
14410
+ const dashboardNavVisibilitySchema = dashboardNavVisibilityBaseSchema.extend({ roles: dashboardNavVisibilityRolesSchema }).strip();
14411
+ const dashboardNavigationConfigSchema = z.object({
14412
+ siteNav: dashboardNavVisibilitySchema.optional(),
14413
+ settingsNav: dashboardNavVisibilitySchema.optional()
14414
+ }).strip();
14415
+ const dashboardConfigSchema = z.object({
14416
+ previewEditingEnabled: z.boolean().optional(),
14417
+ navigation: dashboardNavigationConfigSchema.optional()
14418
+ }).strip();
14419
+ /**
14420
+ * Canonical field schema comes from @riverbankcms/blocks.
14421
+ *
14422
+ * It accepts FieldDefinitionInput (pre-defaults) and outputs FieldDefinition (post-defaults).
14423
+ * This `satisfies` assertion prevents drift in the *accepted wire shape* without forcing
14424
+ * the schema output type to match the input type.
14425
+ */
14426
+ const fieldDefinitionInputSchema = unsafeRetypeZodSchema(fieldSchema);
14427
+ const nodeDefinitionSchema = unsafeRetypeZodSchema(NodeSchema);
14428
+ const sdkLoaderEndpointSchema = z.enum(SUPPORTED_LOADER_ENDPOINTS);
14429
+ const loaderFallbackScalarSchema = z.union([
14430
+ z.string(),
14431
+ z.number(),
14432
+ z.boolean(),
14433
+ z.null()
14434
+ ]);
14435
+ const loaderParamBindingSchema = z.object({ $bind: z.object({
14436
+ from: z.string().min(1, "Binding path is required"),
14437
+ fallback: loaderFallbackScalarSchema.optional()
14438
+ }) });
14439
+ const loaderParamValueSchema = z.union([
14440
+ z.string(),
14441
+ z.number(),
14442
+ z.boolean(),
14443
+ loaderParamBindingSchema
14444
+ ]);
14445
+ const sdkConfigLoaderSchema = z.object({
14446
+ endpoint: sdkLoaderEndpointSchema,
14447
+ params: z.record(z.string(), loaderParamValueSchema),
14448
+ requiredParams: z.array(z.string().min(1)).optional(),
14449
+ mode: z.enum(["server", "client"]).default("server")
14450
+ });
14451
+ const sdkDataLoadersSchema = z.record(z.string(), sdkConfigLoaderSchema).refine((loaders) => Object.keys(loaders).length <= 5, { message: "Maximum 5 data loaders per block" }).optional();
14452
+ const sdkCustomBlockIdSchema = z.string().min(8).regex(/^custom\.[a-z][a-z0-9-]*$/, { message: "Block ID must start with 'custom.' followed by lowercase letters, numbers, or hyphens" }).transform((value) => value);
14453
+ const sdkCustomBlockInputSchema = z.object({
14454
+ id: sdkCustomBlockIdSchema,
14455
+ title: z.string().min(1),
14456
+ titleSource: z.union([z.string().min(1), z.array(z.string().min(1)).min(1)]).optional(),
14457
+ description: z.string().optional(),
14458
+ category: blockCategoryEnum,
14459
+ icon: z.string().optional(),
14460
+ tags: z.array(z.string()).optional(),
14461
+ fields: fieldDefinitionInputSchema.array().min(1),
14462
+ dataLoaders: sdkDataLoadersSchema,
14463
+ layout: NodeSchema.optional(),
14464
+ ...sdkBlockPresentationShape,
14465
+ ...blockSectionDeclarationShape
14466
+ }).superRefine(validateManifestLookField).superRefine(validateSdkBlockDeclaration);
14467
+ const sdkBlockFieldExtensionInputSchema = z.object({
14468
+ fields: fieldDefinitionInputSchema.array().min(1),
14469
+ ...blockSectionDeclarationShape
14470
+ }).superRefine(validateManifestLookField);
14471
+ const sdkBlockFieldExtensionsInputSchema = z.record(z.string().min(1), sdkBlockFieldExtensionInputSchema);
14472
+ const sdkThemePaletteSchema = z.record(z.string(), z.string());
14473
+ const sdkThemeConfigSchema = z.object({ palette: sdkThemePaletteSchema });
14474
+ const sdkSectionBackgroundSchema = z.object({
14475
+ id: z.string().min(1),
14476
+ label: z.string().min(1),
14477
+ token: z.string().min(1)
14478
+ });
14479
+ const sdkSectionOptionsConfigSchema = z.object({
14480
+ backgroundColor: z.boolean().optional(),
14481
+ backgroundImage: z.boolean().optional(),
14482
+ backgroundGradient: z.boolean().optional(),
14483
+ spacing: z.union([z.array(z.enum([
14484
+ "compact",
14485
+ "default",
14486
+ "spacious"
14487
+ ])), z.boolean()]).optional(),
14488
+ textColor: z.boolean().optional()
14489
+ });
14490
+ const sdkContainerOptionsConfigSchema = z.object({
14491
+ maxWidth: z.union([z.array(z.enum([
14492
+ "narrow",
14493
+ "default",
14494
+ "wide",
14495
+ "full"
14496
+ ])), z.boolean()]).optional(),
14497
+ alignment: z.union([z.array(z.enum([
14498
+ "left",
14499
+ "center",
14500
+ "right"
14501
+ ])), z.boolean()]).optional()
14502
+ });
14503
+ const sdkSiteStyleConfigSchema = z.object({
14504
+ sectionBackgrounds: z.array(sdkSectionBackgroundSchema).optional(),
14505
+ sectionOptions: sdkSectionOptionsConfigSchema.optional(),
14506
+ containerOptions: sdkContainerOptionsConfigSchema.optional()
14507
+ });
14508
+ const sdkSiteLayoutConfigSchema = z.object({
14509
+ footerMode: footerModeSchema.optional(),
14510
+ footerBlocks: z.array(z.object({
14511
+ id: z.string().min(1).optional(),
14512
+ kind: z.string().min(1),
14513
+ content: z.record(z.string(), z.unknown()).optional()
14514
+ })).optional()
14515
+ });
14516
+ const sdkFieldSelectOptionSchema = z.object({
14517
+ value: z.string(),
14518
+ label: z.string()
14519
+ });
14520
+ const sdkBlockFieldConfigSchema = z.object({ options: z.array(sdkFieldSelectOptionSchema).optional() });
14521
+ const sdkBlockFieldOptionsMapSchema = z.record(z.string().regex(/^(block\.|custom\.)[a-z][a-z0-9-]*$/, { message: "Block ID must be 'block.*' or 'custom.*' format" }), z.record(z.string().min(1), sdkBlockFieldConfigSchema)).optional();
14522
+ const sdkContentTypeTemplateConfigSchema = z.object({ blocks: z.array(z.unknown()) }).passthrough();
14523
+ const sdkContentTypeOrderingConfigSchema = z.object({ mode: z.enum(["default", "manual"]).optional() }).optional();
14524
+ const sdkContentTypeConfigSchema = z.object({
14525
+ key: z.string().min(1),
14526
+ fields: z.array(z.unknown()),
14527
+ ordering: sdkContentTypeOrderingConfigSchema,
14528
+ template: sdkContentTypeTemplateConfigSchema.nullable().optional()
14529
+ }).passthrough();
14530
+ const sdkContentConfigSchema = z.object({
14531
+ contentTypes: z.array(sdkContentTypeConfigSchema).optional(),
14532
+ pages: z.array(z.unknown()).optional(),
14533
+ entries: z.array(z.unknown()).optional(),
14534
+ navigationMenus: z.array(z.unknown()).optional(),
14535
+ settings: z.unknown().optional()
14536
+ }).passthrough();
14537
+ const SDK_WORKFLOW_ID_MAX_LENGTH = 120;
14538
+ const SDK_WORKFLOW_TITLE_MAX_LENGTH = 120;
14539
+ const SDK_WORKFLOW_DESCRIPTION_MAX_LENGTH = 500;
14540
+ const SDK_WORKFLOW_CONTENT_TYPE_MAX_LENGTH = 120;
14541
+ const SDK_WORKFLOW_ENDPOINT_MAX_LENGTH = 500;
14542
+ const SDK_WORKFLOW_TRIGGER_MAX_COUNT = 5;
14543
+ const SDK_WORKFLOW_TRIGGER_FIELD_MAX_LENGTH = 120;
14544
+ const SDK_WORKFLOW_TRIGGER_CONDITION_MAX_COUNT = 10;
14545
+ const SDK_WORKFLOW_MAX_COUNT = 50;
14546
+ const SDK_WORKFLOW_CONFIRMATION_TITLE_MAX_LENGTH = 120;
14547
+ const SDK_WORKFLOW_CONFIRMATION_BODY_MAX_LENGTH = 500;
14548
+ const SDK_WORKFLOW_ID_PATTERN = /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/;
14549
+ const SDK_WORKFLOW_ENDPOINT_PATTERN = /^\//;
14550
+ const sdkWorkflowIdSchema = z.string().min(1).max(SDK_WORKFLOW_ID_MAX_LENGTH).regex(SDK_WORKFLOW_ID_PATTERN, { message: "Workflow ID must start with a lowercase letter and use lowercase letters, numbers, dots, underscores, or hyphens" });
14551
+ const sdkWorkflowEndpointSchema = z.string().min(1).max(SDK_WORKFLOW_ENDPOINT_MAX_LENGTH).regex(SDK_WORKFLOW_ENDPOINT_PATTERN, { message: "Workflow endpoint must be a site-relative path starting with \"/\"" });
14552
+ const sdkWorkflowManualTriggerSchema = z.object({ type: z.literal("manual") }).strict();
14553
+ const sdkWorkflowTriggerFieldSchema = z.string().trim().min(1).max(SDK_WORKFLOW_TRIGGER_FIELD_MAX_LENGTH).refine((field) => !field.includes("."), { message: "Workflow trigger fields must be top-level content field IDs, not paths" });
14554
+ const sdkWorkflowFieldChangedConditionSchema = z.object({
14555
+ field: sdkWorkflowTriggerFieldSchema,
14556
+ changed: z.literal(true)
14557
+ }).strict();
14558
+ const sdkWorkflowFieldEmptyConditionSchema = z.object({
14559
+ field: sdkWorkflowTriggerFieldSchema,
14560
+ isEmpty: z.boolean()
14561
+ }).strict();
14562
+ const sdkWorkflowEntryDraftUpdatedTriggerSchema = z.object({
14563
+ type: z.literal("entry.draft.updated"),
14564
+ when: z.array(z.union([sdkWorkflowFieldChangedConditionSchema, sdkWorkflowFieldEmptyConditionSchema])).min(1).max(SDK_WORKFLOW_TRIGGER_CONDITION_MAX_COUNT)
14565
+ }).strict();
14566
+ const sdkWorkflowTriggerSchema = z.discriminatedUnion("type", [sdkWorkflowManualTriggerSchema, sdkWorkflowEntryDraftUpdatedTriggerSchema]);
14567
+ const sdkWorkflowConfirmationSchema = z.object({
14568
+ title: z.string().trim().min(1).max(SDK_WORKFLOW_CONFIRMATION_TITLE_MAX_LENGTH),
14569
+ body: z.string().trim().min(1).max(SDK_WORKFLOW_CONFIRMATION_BODY_MAX_LENGTH).optional()
14570
+ }).strict();
14571
+ const sdkWorkflowConfigSchema = z.object({
14572
+ id: sdkWorkflowIdSchema,
14573
+ title: z.string().trim().min(1).max(SDK_WORKFLOW_TITLE_MAX_LENGTH),
14574
+ description: z.string().trim().min(1).max(SDK_WORKFLOW_DESCRIPTION_MAX_LENGTH).optional(),
14575
+ contentType: z.string().trim().min(1).max(SDK_WORKFLOW_CONTENT_TYPE_MAX_LENGTH),
14576
+ endpoint: sdkWorkflowEndpointSchema,
14577
+ triggers: z.array(sdkWorkflowTriggerSchema).min(1).max(SDK_WORKFLOW_TRIGGER_MAX_COUNT),
14578
+ confirmation: sdkWorkflowConfirmationSchema.optional()
14579
+ }).strict();
14580
+ const sdkWorkflowsSchema = z.array(sdkWorkflowConfigSchema).max(SDK_WORKFLOW_MAX_COUNT, "Maximum 50 workflows per site").refine((workflows) => {
14581
+ const ids = workflows.map((workflow) => workflow.id);
14582
+ return ids.length === new Set(ids).size;
14583
+ }, { message: "Workflow IDs must be unique" }).optional();
14584
+ const riverbankSiteConfigInputSchema = z.object({
14585
+ siteId: z.string().uuid(),
14586
+ builtInBlocks: builtInBlockKindsSchema.optional(),
14587
+ previewUrl: z.string().url().optional(),
14588
+ liveUrl: z.string().url().optional(),
14589
+ theme: sdkThemeConfigSchema.optional(),
14590
+ styles: sdkSiteStyleConfigSchema.optional(),
14591
+ layout: sdkSiteLayoutConfigSchema.optional(),
14592
+ customBlocks: z.array(sdkCustomBlockInputSchema).max(100, "Maximum 100 custom blocks per site").refine((blocks) => {
14593
+ const ids = blocks.map((b) => b.id);
14594
+ return ids.length === new Set(ids).size;
14595
+ }, { message: "Block IDs must be unique" }).optional(),
14596
+ blockFieldOptions: sdkBlockFieldOptionsMapSchema,
14597
+ blockFieldExtensions: sdkBlockFieldExtensionsInputSchema.optional(),
14598
+ workflows: sdkWorkflowsSchema,
14599
+ content: sdkContentConfigSchema.optional(),
14600
+ contentDir: z.string().optional(),
14601
+ sync: z.object({ existingEntries: z.enum(["skip", "update"]).optional() }).optional(),
14602
+ deploy: z.object({
14603
+ verifyCommand: z.string().optional(),
14604
+ prebuildOutput: z.string().optional()
14605
+ }).optional(),
14606
+ themeSource: z.enum(["local", "cms"]).optional(),
14607
+ activeTheme: z.string().optional(),
14608
+ enablePortal: z.boolean().optional(),
14609
+ dashboard: dashboardConfigSchema.optional()
14610
+ }).strict();
14611
+ const pushSdkConfigBodySchema = z.object({
14612
+ config: riverbankSiteConfigInputSchema,
14613
+ forceContentTypes: z.boolean().optional(),
14614
+ mode: z.enum(["apply", "preview"]).optional(),
14615
+ expectedMailingListProviderScope: z.unknown().transform((value, ctx) => {
14616
+ if (value === undefined) return undefined;
14617
+ const parsed = parseMailingListProviderScope(value);
14618
+ if (parsed.ok) return parsed.value;
14619
+ ctx.addIssue({
14620
+ code: "custom",
14621
+ message: "Invalid mailing-list provider scope"
14622
+ });
14623
+ return z.NEVER;
14624
+ }).optional()
14625
+ }).strict();
14626
+
14627
+ //#endregion
14628
+ //#region ../api/src/sdkContracts.ts
14629
+ const ignoredDocumentSyncSectionWarnings = {
14630
+ pages: "content.pages is ignored by push-config; put pages in content/pages/*.json and use `riverbankcms push pages` (or `riverbankcms push`) to sync them.",
14631
+ entries: "content.entries is ignored by push-config; put entries in content/entries/<type>/*.json and use `riverbankcms push entries` (or `riverbankcms push`) to sync them.",
14632
+ navigationMenus: "content.navigationMenus is ignored by push-config; put navigation in content/navigation.json and use `riverbankcms push navigation` (or `riverbankcms push`) to sync it."
14633
+ };
14634
+ function formatIgnoredDocumentSyncSectionWarning(section) {
14635
+ return ignoredDocumentSyncSectionWarnings[section];
14636
+ }
14637
+ const SDK_PATCH_STALE_CONTENT_FIELD = "baseContentHash";
14638
+ const SDK_PATCH_STALE_CONTENT_CODE = "STALE_CONTENT";
14639
+
14640
+ //#endregion
14641
+ //#region ../management-client/src/blocks.ts
14642
+ /**
14643
+ * Block Operations for Management Client
14644
+ */
14645
+ /**
14646
+ * Discriminated stale-conflict error for `blocks.patch`: the block's draft
14647
+ * changed since the caller's read. `currentHash` is the server's current
14648
+ * draft content hash — retry the patch with it as `baseContentHash`.
14649
+ * Extracted from the typed `SdkPatchPageBlockStaleContentFieldError` wire
14650
+ * contract, so callers never parse prose.
14651
+ */
14652
+ var BlockPatchStaleContentError = class extends ManagementApiError {
14653
+ constructor(source, currentHash) {
14654
+ super(source.message, source.code, source.details, source.statusCode, source.request);
14655
+ this.kind = "stale-content";
14656
+ this.name = "BlockPatchStaleContentError";
14657
+ this.currentHash = currentHash;
14658
+ }
11943
14659
  };
11944
14660
  function isBlockPatchStaleContentError(error) {
11945
14661
  return error instanceof BlockPatchStaleContentError;
@@ -14388,25 +17104,6 @@ function parsePublicProductCategorySelector(value) {
14388
17104
  return undefined;
14389
17105
  }
14390
17106
 
14391
- //#endregion
14392
- //#region ../api/src/request-error.ts
14393
- var ApiRequestError = class extends Error {
14394
- constructor(message, options) {
14395
- super(message);
14396
- this.name = "ApiRequestError";
14397
- this.endpoint = options.endpoint;
14398
- this.status = options.status;
14399
- this.method = options.method;
14400
- this.auth = options.auth;
14401
- this.requestId = options.requestId;
14402
- this.body = options.body;
14403
- this.cause = options.cause;
14404
- this.errorCode = options.errorCode;
14405
- this.tag = options.tag;
14406
- this.retryAfterMs = options.retryAfterMs;
14407
- }
14408
- };
14409
-
14410
17107
  //#endregion
14411
17108
  //#region ../api/src/token.ts
14412
17109
  async function getAuthHeaders(supabase) {
@@ -14430,6 +17127,18 @@ async function getAuthHeaders(supabase) {
14430
17127
  //#region ../surfaces/src/index.ts
14431
17128
  const SURFACE_IDS = ["core", "macadamia"];
14432
17129
  const DEFAULT_SURFACE_ID = "core";
17130
+ const SURFACE_ASSISTANT_MARKS = {
17131
+ core: {
17132
+ src: "/logo.svg",
17133
+ width: 22,
17134
+ height: 20
17135
+ },
17136
+ macadamia: {
17137
+ src: "/surfaces/macadamia/assistant-mark.svg",
17138
+ width: 26,
17139
+ height: 26
17140
+ }
17141
+ };
14433
17142
  const SURFACE_DOCUMENT_DATA_ATTRIBUTE = "data-dashboard-surface";
14434
17143
  function isSurfaceId(value) {
14435
17144
  return typeof value === "string" && SURFACE_IDS.includes(value);
@@ -14895,7 +17604,6 @@ function getRevalidateTag() {
14895
17604
  }
14896
17605
  return revalidateTag;
14897
17606
  }
14898
- let sdkVersion;
14899
17607
  /**
14900
17608
  * Generate a UUID, with fallback for environments where crypto.randomUUID isn't available.
14901
17609
  */
@@ -14910,19 +17618,6 @@ function generateRequestId() {
14910
17618
  });
14911
17619
  }
14912
17620
  /**
14913
- * Set the SDK version to be included in API request headers
14914
- * Called by the SDK during initialization
14915
- */
14916
- function setSdkVersion(version) {
14917
- sdkVersion = version;
14918
- }
14919
- /**
14920
- * Get the currently set SDK version
14921
- */
14922
- function getSdkVersion() {
14923
- return sdkVersion;
14924
- }
14925
- /**
14926
17621
  * Parse the Retry-After header value to milliseconds.
14927
17622
  * The header can be either:
14928
17623
  * - A number of seconds (e.g., "120")
@@ -15145,7 +17840,7 @@ async function createAuthenticatedApiClient(supabase) {
15145
17840
  }
15146
17841
  return createCMSClient(authHeaders);
15147
17842
  }
15148
- function createRawCMSClient(getHeaders = () => ({}), baseUrl) {
17843
+ function createRawCMSClient(getHeaders = () => ({}), baseUrl, { sdkVersion } = {}) {
15149
17844
  return ({ endpoint, body, params, options = {} }) => {
15150
17845
  const plan = buildApiRequestPlan({
15151
17846
  endpoint,
@@ -15279,8 +17974,8 @@ function createParsedClient(rawClient) {
15279
17974
  return parseSuccessResponse(endpoint, response, config);
15280
17975
  };
15281
17976
  }
15282
- function createCMSClient(headers = {}, baseUrl) {
15283
- return createParsedClient(createRawCMSClient(() => headers, baseUrl));
17977
+ function createCMSClient(headers = {}, baseUrl, options) {
17978
+ return createParsedClient(createRawCMSClient(() => headers, baseUrl, options));
15284
17979
  }
15285
17980
  const createServerAPIClient = (apiKey, baseUrl) => {
15286
17981
  const normalizedApiKey = normalizeCmsApiKey(apiKey);
@@ -15290,11 +17985,11 @@ const createServerAPIClient = (apiKey, baseUrl) => {
15290
17985
  const authHeaders = { Authorization: `Bearer ${normalizedApiKey}` };
15291
17986
  return createCMSClient(authHeaders, baseUrl);
15292
17987
  };
15293
- function createInternalAPIClient(baseUrl) {
15294
- return createCMSClient({}, baseUrl);
17988
+ function createInternalAPIClient(baseUrl, options) {
17989
+ return createCMSClient({}, baseUrl, options);
15295
17990
  }
15296
- function createBearerAPIClient(token, baseUrl) {
15297
- return createParsedClient(createRawCMSClient(() => ({ Authorization: `Bearer ${typeof token === "function" ? token() : token}` }), baseUrl));
17991
+ function createBearerAPIClient(token, baseUrl, options) {
17992
+ return createParsedClient(createRawCMSClient(() => ({ Authorization: `Bearer ${typeof token === "function" ? token() : token}` }), baseUrl, options));
15298
17993
  }
15299
17994
  /** Positive receipt evidence is kept distinct from an HTTP or request failure. */
15300
17995
  function requireUsableCommitProjection(response, parsed) {
@@ -15302,13 +17997,6 @@ function requireUsableCommitProjection(response, parsed) {
15302
17997
  throw new ApiCommittedWriteNotice(pageCommitConfirmationEnvelopeSchema.parse(parsed).data);
15303
17998
  }
15304
17999
 
15305
- //#endregion
15306
- //#region ../api/src/appointmentServiceVisibility.ts
15307
- const APPOINTMENT_SERVICE_VISIBILITY_VALUES = ["public", "unlisted"];
15308
- function isAppointmentServiceVisibility(value) {
15309
- return value === "public" || value === "unlisted";
15310
- }
15311
-
15312
18000
  //#endregion
15313
18001
  //#region ../api/src/appointmentServiceLocation.ts
15314
18002
  const APPOINTMENT_SERVICE_LOCATION_TYPE_VALUES = [
@@ -15329,67 +18017,37 @@ const APPOINTMENT_RESERVATION_STATUS_VALUES = [
15329
18017
  ];
15330
18018
 
15331
18019
  //#endregion
15332
- //#region ../api/src/utils/isRecord.ts
15333
- function isRecord$5(value) {
15334
- return typeof value === "object" && value !== null && !Array.isArray(value);
18020
+ //#region ../api/src/wholeSeriesRuns.ts
18021
+ const DELETE_EVENT_OFFERING_CONFLICT_REASONS = [
18022
+ "has_registrations",
18023
+ "has_payments",
18024
+ "published_page",
18025
+ "inconsistent_ownership"
18026
+ ];
18027
+ function isDeleteEventOfferingConflictReason(value) {
18028
+ return DELETE_EVENT_OFFERING_CONFLICT_REASONS.some((reason) => reason === value);
15335
18029
  }
18030
+ const EVENT_SERIES_REGISTRATION_STATUSES = [
18031
+ "pending_payment",
18032
+ "enrolled",
18033
+ "waitlist",
18034
+ "cancelled",
18035
+ "completed"
18036
+ ];
15336
18037
 
15337
18038
  //#endregion
15338
- //#region ../api/src/utils/error-propagation.ts
15339
- /**
15340
- * Extracts an error message from an API error response body.
15341
- * Checks for our standard API error format: { error: { message: "..." } }
15342
- * Also handles legacy formats with `error` or `message` as strings.
15343
- */
15344
- function extractApiErrorMessage(body) {
15345
- if (!isRecord$5(body)) {
15346
- return null;
15347
- }
15348
- if (isRecord$5(body.error)) {
15349
- if (typeof body.error.message === "string" && body.error.message.trim().length > 0) {
15350
- return body.error.message;
15351
- }
15352
- }
15353
- if (typeof body.error === "string" && body.error.trim().length > 0) {
15354
- return body.error;
15355
- }
15356
- if (typeof body.message === "string" && body.message.trim().length > 0) {
15357
- return body.message;
15358
- }
15359
- return null;
15360
- }
15361
- /**
15362
- * Normalizes various error types and throws with an appropriate message.
15363
- *
15364
- * - For ApiRequestError: extracts message from body.error or body.message
15365
- * - For Error instances: re-throws as-is
15366
- * - For unknown errors: wraps with fallback message
15367
- *
15368
- * @param error - The error to normalize
15369
- * @param fallback - Fallback message if no specific message can be extracted
15370
- * @throws {Error} Always throws - this function never returns
15371
- */
15372
- function normalizeApiError(error, fallback) {
15373
- if (error instanceof ApiRequestError) {
15374
- const message = extractApiErrorMessage(error.body);
15375
- throw new ApiRequestError(message ?? fallback, {
15376
- endpoint: error.endpoint,
15377
- status: error.status,
15378
- method: error.method,
15379
- auth: error.auth,
15380
- requestId: error.requestId,
15381
- body: error.body,
15382
- cause: error,
15383
- errorCode: error.errorCode,
15384
- tag: error.tag,
15385
- retryAfterMs: error.retryAfterMs
15386
- });
15387
- }
15388
- if (error instanceof Error) {
15389
- throw error;
15390
- }
15391
- throw new Error(fallback);
15392
- }
18039
+ //#region ../api/src/recordedPayment.ts
18040
+ /** Facts shared by every staff-recorded payment. Future dates need the site's timezone. */
18041
+ const recordedPaymentFieldsSchema = z.object({
18042
+ method: z.enum(OFFLINE_PAYMENT_METHODS),
18043
+ paidOn: z.iso.date()
18044
+ });
18045
+ /** A newly added booking defaults its payment date to today in the site's timezone. */
18046
+ const handAddedPaidPaymentSchema = recordedPaymentFieldsSchema.extend({
18047
+ kind: z.literal("paid"),
18048
+ amountCents: z.number().int().positive(),
18049
+ paidOn: recordedPaymentFieldsSchema.shape.paidOn.optional()
18050
+ }).strict();
15393
18051
 
15394
18052
  //#endregion
15395
18053
  //#region ../api/src/bookingActivity.ts
@@ -15409,6 +18067,8 @@ const BOOKING_ACTIVITY_TYPE_VALUES = [
15409
18067
  "event_booking_restored",
15410
18068
  "waitlist_joined",
15411
18069
  "balance_paid",
18070
+ "payment_recorded",
18071
+ "payment_waived",
15412
18072
  "appointment_cancelled",
15413
18073
  "booking_closure_created",
15414
18074
  "booking_closure_removed",
@@ -15512,18 +18172,23 @@ const PERSON_DELETION_BLOCKER_CATEGORY_VALUES = [
15512
18172
  "uploads",
15513
18173
  "contact_corrections"
15514
18174
  ];
15515
-
15516
- //#endregion
15517
- //#region ../api/src/wholeSeriesRuns.ts
15518
- const DELETE_EVENT_OFFERING_CONFLICT_REASONS = [
15519
- "has_registrations",
15520
- "has_payments",
15521
- "published_page",
15522
- "inconsistent_ownership"
18175
+ const PERSON_REMOVAL_BLOCKER_CATEGORIES = [
18176
+ "upcoming_bookings",
18177
+ "memberships",
18178
+ "payment_plans",
18179
+ "flexible_balances",
18180
+ "unused_passes",
18181
+ "credit",
18182
+ "gifts",
18183
+ "purchases",
18184
+ "uploads"
18185
+ ];
18186
+ const PERSON_REMOVAL_CLEANUP_KINDS = [
18187
+ "storage",
18188
+ "stripe",
18189
+ "google_calendar",
18190
+ "newsletter"
15523
18191
  ];
15524
- function isDeleteEventOfferingConflictReason(value) {
15525
- return DELETE_EVENT_OFFERING_CONFLICT_REASONS.some((reason) => reason === value);
15526
- }
15527
18192
 
15528
18193
  //#endregion
15529
18194
  //#region ../api/src/eventAttendanceLabels.ts
@@ -16687,6 +19352,17 @@ async function adminChangePlan(apiClient, body, options) {
16687
19352
  return normalizeApiError(error, "Failed to update billing plan");
16688
19353
  }
16689
19354
  }
19355
+ async function adminGrantTrial(apiClient, body, options) {
19356
+ try {
19357
+ return unwrapResponse(await apiClient({
19358
+ endpoint: "adminGrantTrial",
19359
+ body,
19360
+ options
19361
+ }));
19362
+ } catch (error) {
19363
+ return normalizeApiError(error, "Failed to give free time");
19364
+ }
19365
+ }
16690
19366
  async function listAdminInvites(apiClient, params = {}, options) {
16691
19367
  try {
16692
19368
  return unwrapResponse(await apiClient({
@@ -16891,7 +19567,6 @@ async function adminClearSitePublicCache(apiClient, siteId, options) {
16891
19567
 
16892
19568
  //#endregion
16893
19569
  //#region ../api/src/accessAdmin.ts
16894
- const STAFF_OPERATIONAL_CAPABILITIES = ["appointments", "event_ops"];
16895
19570
  function getDefaultStaffCapabilities(surfaces) {
16896
19571
  if (surfaces.events && !surfaces.appointments) return ["event_ops"];
16897
19572
  return ["appointments"];
@@ -17056,6 +19731,7 @@ async function acceptSiteInvitation(apiClient, { body, options }) {
17056
19731
 
17057
19732
  //#endregion
17058
19733
  //#region ../api/src/staffAccess.ts
19734
+ const STAFF_OPERATIONAL_CAPABILITIES = ["appointments", "event_ops"];
17059
19735
  function roleAtLeast$1(role, minimum) {
17060
19736
  return SITE_ROLE_PERMISSION_LEVEL[role] >= SITE_ROLE_PERMISSION_LEVEL[minimum];
17061
19737
  }
@@ -17223,6 +19899,21 @@ const FLEXIBLE_BALANCE_ADMIN_ACTION_SOURCES = [
17223
19899
  "event_attendee_detail",
17224
19900
  "event_series_registration_detail"
17225
19901
  ];
19902
+ const settlementAuditSchema = {
19903
+ operationId: z.string().uuid(),
19904
+ orderItemIds: z.array(z.string().uuid()).nonempty().optional(),
19905
+ amountCents: z.number().int().positive().optional().nullable(),
19906
+ externalReference: z.string().optional().nullable(),
19907
+ note: z.string().optional().nullable()
19908
+ };
19909
+ const settleShopOrderPaymentSchema = z.union([z.object({
19910
+ ...settlementAuditSchema,
19911
+ action: z.enum(["mark_paid", "record_partial_payment"]),
19912
+ ...recordedPaymentFieldsSchema.shape
19913
+ }), z.object({
19914
+ ...settlementAuditSchema,
19915
+ action: z.literal("mark_waived")
19916
+ })]);
17226
19917
 
17227
19918
  //#endregion
17228
19919
  //#region ../api/src/navigation/visibility.ts
@@ -24898,4 +27589,4 @@ function optionalTitle(data) {
24898
27589
  }
24899
27590
 
24900
27591
  //#endregion
24901
- export { normalizeMediaValueToIdentifierOnly as $, createCommandContext as $t, formatDiffDetail as A, getEnvPrefix as An, navigationIdentity as At, buildFieldChanges as B, portablePageDesignSchema as Bn, preparePushConfigTarget as Bt, reportFromOutcomes as C, requireEnvironmentWriteAuthorization as Cn, fileExists$1 as Ct, normalizeRemoteHomepageReference as D, transportSuggestion as Dn, writeJsonFileAtomic as Dt, createEmptySyncResult as E, formatError as En, writeJsonFile$1 as Et, calculatePullDiff as F, createManagementClient as Fn, navigationCommandItemToManagementInput as Ft, syncLocalMediaForPull as G, APPOINTMENT_SERVICE_VISIBILITY_VALUES as Gt, formatFieldPath as H, API_ENDPOINTS as Hn, extractApiErrorMessage as Ht, buildJsonDiff as I, ManagementApiError as In, normalizeNavigationConfigItemForCommand as It, admitBinaryWriteDestination as J, ApiRequestError as Jt, applyLocalMediaPush as K, createBearerAPIClient as Kt, hasPageOperations as L, sdkEventScheduleSchema as Ln, applyPushConfigPlan as Lt, hasPendingChanges as M, isLocalConfigured as Mn, convertPulledFooterToLocal as Mt, findChangedFieldsWithValues as N, isRemoteConfigured as Nn, convertPulledNavigationToLocal as Nt, calculateDiff as O, CliExit as On, isMediaValue as Ot, formatHumanDiff as P, loadSdkEnvironment as Pn, convertPulledPagesToLocal as Pt, normalizeMediaIdentifierInput as Q, createClientForEnv as Qt, pageMetadataForUpsert as R, sdkSettingsUpdateSchema as Rn, createPushConfigCommand as Rt, planCliCommandBatchForBindings as S, planEnvironmentWriteAuthorization as Sn, ensureDir$1 as St, executeResidualSettingsSyncPlan as T, ErrorCodes as Tn, readJsonFile as Tt, revalidateLocalMediaPushPlan as U, ApiEnvelopeError as Un, normalizeApiError as Ut, formatFieldChange as V, sdkConfiguredBookingVerticalConfigSchema as Vn, revalidatePushConfigPlan as Vt, syncLocalMediaForPush as W, unwrapResponse as Wn, isRecord$5 as Wt, writeBinaryFileAtomic as X, bindRequestTimeout as Xt, readStableBinaryFile as Y, mapEnvironmentExecutionOutcomesJson as Yt, sha256 as Z, confirmAction as Zt, commandExecutionToReport as _, withErrorHandling as _n, metadataFilePath as _t, executeEntryCommandPlanAsReport as a, getOutputContext as an, readEntries as at, executeCompiledCliCommandPlanAsReport as b, parseEnvironmentSelection as bn, writeMetadataFile as bt, titleFromEntryData as c, managementClientConfigFor as cn, readEventsMeta as ct, unsafeAsCliEntryIdentifier as d, parseJsonData as dn, readNavigationMeta as dt, createGetCommand as en, walkLocalContentMedia as et, unsafeAsCliFormSlug as f, parsePaginationOptions as fn, readPagesMeta as ft, commandCompileFailureToReport as g, withConfirmation as gn, ensureMetadataDir as gt, mutationCounterForEffect as h, withCommandContext as hn, writeMediaManifest as ht, entrySiteCommandTypes as i, formatDateShort as in, findLegacyEntryFiles as it, formatDiffSummary as j, getEnvironmentDebugInfo as jn, navigationIdentityMap as jt, calculateFooterDiff as k, createOutput as kn, duplicateNavigationIdentityMessage as kt, unsafeAsCliBlockIdentifier as l, mapEntryForOutput as ln, readFooterMeta as lt, commandReportToSyncResult as m, runWithInvocation as mn, readVenuesMeta as mt, entryCommandCompileErrorToReport as n, createOutputForCommand as nn, downloadMedia as nt, executeEntryCommandPlanAsSyncResult as o, getOutputOnlyContext as on, readAllMeta as ot, unsafeAsCliPageIdentifier as p, resolveCommandPath as pn, readSettingsMeta as pt, prepareLocalMediaForPush as q, deriveCmsApiUrlFromDashboardUrl as qt, entryCommandReportToSyncResult as r, currentInvocationEnvelope as rn, extractMediaIdentifiers as rt, syncResultForEntryCommandCompileError as s, handleCommandError as sn, readEventCategoriesMeta as st, compileEntryPushCommands as t, createListCommand as tn, buildStorageUrl as tt, unsafeAsCliContentTypeKey as u, parseJsonArray as un, readFormsMeta as ut, commandReportHasFailures as v, withOutputOnlyErrorHandling as vn, metadataTargetFromEnvironment as vt, mergeManySyncResults as w, withRiverbankEnv as wn, listFiles as wt, formatManagementError as x, planEnvironmentExecution as xn, writeMetadataRecordMap as xt, executeCliCommandPlan as y, executeEnvironmentPlan as yn, readMetadataFile as yt, calculateEntryDiffs as z, linkPayloadSchema as zn, loadPushConfigTarget as zt };
27592
+ export { normalizeMediaValueToIdentifierOnly as $, normalizeApiError as $n, formatDateShort as $t, formatDiffDetail as A, createManagementClient as An, navigationIdentity as At, buildFieldChanges as B, planRepeaterGridColumnChange as Bn, preparePushConfigTarget as Bt, reportFromOutcomes as C, CliExit as Cn, fileExists$1 as Ct, normalizeRemoteHomepageReference as D, isLocalConfigured as Dn, writeJsonFileAtomic as Dt, createEmptySyncResult as E, getEnvironmentDebugInfo as En, writeJsonFile$1 as Et, calculatePullDiff as F, fieldSchema as Fn, navigationCommandItemToManagementInput as Ft, syncLocalMediaForPull as G, resolveRepeaterGridColumnsForBreakpoint as Gn, bindRequestTimeout as Gt, formatFieldPath as H, buildRepeaterGridLayout as Hn, createBearerAPIClient as Ht, buildJsonDiff as I, normalizeSelectSource as In, normalizeNavigationConfigItemForCommand as It, admitBinaryWriteDestination as J, typeBasedLayout as Jn, createCommandContext as Jt, applyLocalMediaPush as K, resolveRepeaterGridRowFieldId as Kn, confirmAction as Kt, hasPageOperations as L, parseFieldDefinition as Ln, applyPushConfigPlan as Lt, hasPendingChanges as M, PUBLIC_FILE_FIELD_KINDS as Mn, convertPulledFooterToLocal as Mt, findChangedFieldsWithValues as N, createSdkSelectField as Nn, convertPulledNavigationToLocal as Nt, calculateDiff as O, isRemoteConfigured as On, isMediaValue as Ot, formatHumanDiff as P, createSiteOptionSelectField as Pn, convertPulledPagesToLocal as Pt, normalizeMediaIdentifierInput as Q, extractApiErrorMessage as Qn, currentInvocationEnvelope as Qt, pageMetadataForUpsert as R, parseFieldDefinitions as Rn, createPushConfigCommand as Rt, planCliCommandBatchForBindings as S, transportSuggestion as Sn, ensureDir$1 as St, executeResidualSettingsSyncPlan as T, getEnvPrefix as Tn, readJsonFile as Tt, revalidateLocalMediaPushPlan as U, parseRepeaterGridInteger as Un, deriveCmsApiUrlFromDashboardUrl as Ut, formatFieldChange as V, projectRepeaterGridLayoutForRendering as Vn, revalidatePushConfigPlan as Vt, syncLocalMediaForPush as W, resolveRepeaterGridColumnFieldId as Wn, mapEnvironmentExecutionOutcomesJson as Wt, writeBinaryFileAtomic as X, bindingSchema as Xn, createListCommand as Xt, readStableBinaryFile as Y, NodeSchema as Yn, createGetCommand as Yt, sha256 as Z, ManagementApiError as Zn, createOutputForCommand as Zt, commandExecutionToReport as _, planEnvironmentWriteAuthorization as _n, metadataFilePath as _t, executeEntryCommandPlanAsReport as a, parseJsonArray as an, portablePageDesignSchema as ar, readEntries as at, executeCompiledCliCommandPlanAsReport as b, ErrorCodes as bn, writeMetadataFile as bt, titleFromEntryData as c, resolveCommandPath as cn, ApiEnvelopeError as cr, readEventsMeta as ct, unsafeAsCliEntryIdentifier as d, withConfirmation as dn, readNavigationMeta as dt, getOutputContext as en, isRecord$5 as er, walkLocalContentMedia as et, unsafeAsCliFormSlug as f, withErrorHandling as fn, readPagesMeta as ft, commandCompileFailureToReport as g, planEnvironmentExecution as gn, ensureMetadataDir as gt, mutationCounterForEffect as h, parseEnvironmentSelection as hn, writeMediaManifest as ht, entrySiteCommandTypes as i, mapEntryForOutput as in, linkPayloadSchema as ir, findLegacyEntryFiles as it, formatDiffSummary as j, DEFAULT_MEDIA_FIELD_KINDS as jn, navigationIdentityMap as jt, calculateFooterDiff as k, loadSdkEnvironment as kn, duplicateNavigationIdentityMessage as kt, unsafeAsCliBlockIdentifier as l, runWithInvocation as ln, unwrapResponse as lr, readFooterMeta as lt, commandReportToSyncResult as m, executeEnvironmentPlan as mn, readVenuesMeta as mt, entryCommandCompileErrorToReport as n, handleCommandError as nn, sdkEventScheduleSchema as nr, downloadMedia as nt, executeEntryCommandPlanAsSyncResult as o, parseJsonData as on, sdkConfiguredBookingVerticalConfigSchema as or, readAllMeta as ot, unsafeAsCliPageIdentifier as p, withOutputOnlyErrorHandling as pn, readSettingsMeta as pt, prepareLocalMediaForPush as q, resolveRepeaterGridSpanFieldId as qn, createClientForEnv as qt, entryCommandReportToSyncResult as r, managementClientConfigFor as rn, sdkSettingsUpdateSchema as rr, extractMediaIdentifiers as rt, syncResultForEntryCommandCompileError as s, parsePaginationOptions as sn, API_ENDPOINTS as sr, readEventCategoriesMeta as st, compileEntryPushCommands as t, getOutputOnlyContext as tn, ApiRequestError as tr, buildStorageUrl as tt, unsafeAsCliContentTypeKey as u, withCommandContext as un, readFormsMeta as ut, commandReportHasFailures as v, requireEnvironmentWriteAuthorization as vn, metadataTargetFromEnvironment as vt, mergeManySyncResults as w, createOutput as wn, listFiles as wt, formatManagementError as x, formatError as xn, writeMetadataRecordMap as xt, executeCliCommandPlan as y, withRiverbankEnv as yn, readMetadataFile as yt, calculateEntryDiffs as z, resolveIconPickerPreset as zn, loadPushConfigTarget as zt };