riverbankcms 0.9.0 → 0.10.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,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { $ as generateBaseInteractiveCSS, $n as parseBlockColorToken, $t as NAV_LINK_BORDER_RADIUS_MAP, A as getGradientPreset, An as buildThemeTokens, At as resolveThemeColorReferenceValue, B as generateCssVars, Bn as surfaceForegroundValue, Bt as eyebrowTreatmentValues, C as retargetCompiledBoundaryRuntimeForContentAdjacency, Cn as textColorStyle, Ct as MQ_MD_DOWN, D as compileTransitionRuntime, Dn as resolveSurfaceForeground, Dt as headerVariantValues, E as getCuratedSiteStyle, En as surfaceToneFromForegroundToken, Et as MQ_TABLET_ONLY, F as parseBackgroundAppearanceColorToken, Fn as hexToRgb, Ft as assertNever$4, G as resolvedButtonColourDeclarations, Gn as mediaIdentifierSchema, Gt as offeringsPreviewCompositionTreatmentValues, H as PUBLIC_BUTTON_ROLES, Hn as cardRoleSchema, Ht as headingDecorationColorTokenValues, I as defineBlockDesignMetadata, In as oklchToHexGamut, It as defineBlockDesignCapability, J as contextualButtonSurfaceStyles, Jn as persistableMediaSchema, Jt as visibleHeadingDecorationFamilyValues, K as resolvedButtonFocusDeclarations, Kn as mediaSchema, Kt as testimonialsCompositionTreatmentValues, L as require_jsx_runtime, Ln as DEFAULT_SURFACE_TEXT_COLOR, Lt as defineFrameCapability, M as boundaryRuntimeMetricsForTransition, Mn as ORDINARY_TEXT_MIN_CONTRAST_RATIO, Mt as pageDesignEditorBackgroundOverrideOwner, N as backgroundAppearanceColorTokenNames, Nn as contrastRatio, O as gradientToCss, On as isOnColorBackgroundCandidateToken, Ot as isCssColorLiteral, P as backgroundAppearanceQuickColorTokenNames, Pn as featuresFromHex, Pt as asDesignBlockId, Q as generateEffectsCSS, Qn as unsafeAsOpaqueButtonColourToken, Qt as parseCustomCssRules, R as require_react, Rn as surfaceForegroundDeclarations, Rt as defineNoBlockDesignCapability, Sn as rgbColorValue, St as MQ_LG_UP, T as readHeaderNavFontSizePreference, Tn as selectLogoVariant, Tt as MQ_SM_UP, U as generateButtonCss, Un as cardVariantRoleValues, Ut as introCompositionTreatmentValues, Vn as generateShadowValue$1, Vt as featureCardCompositionTreatmentValues, W as RESOLVED_BUTTON_DISABLED_DECLARATIONS, Wn as normalizeCardRole, Wt as makeBoundaryId, X as resolveContextualButtonRole, Xn as VARIANT_ROLES, Xt as getComponentShadowVar, Y as deriveButtonSurfaceContext, Yn as footerModeSchema, Yt as getComponentBorderWidthPx, Z as generateDefaultButtonSystem, Zn as buttonVariantIdSchema, Zt as parseBlockCustomCssRules, _ as entryIdentifierForCommand, _n as TokenResolver, _t as renderHeaderPaintTargetRules, a as classifySdkEventBookingOffer, an as INPUT_FONT_SIZE_MAP, ar as createEventSeriesRef, at as parseThemeForPersistence, b as pagePathForCommand, bn as cssVarValue, bt as BREAKPOINT_SM, c as stableStringify, cn as ACCORDION_OPEN_ACCENT_THICKNESS, cr as createNavigationRef, ct as generateShadowValue, d as mapEventCommandContent, dn as ACCORDION_TRIGGER_LETTER_SPACING, dr as createVenueRef, dt as themeSchema, en as NAV_LINK_LETTER_SPACING_MAP, er as createBlockRef, et as generateInteractiveChildInheritanceCSS, f as evaluateSiteCommandBatchExposure, fn as ICON_CONFIG, fr as refSlug, ft as boxBackgroundOverlaySchema, g as blockOrderIdentifiersForCommand, gn as generateCardCss, gt as getHeaderPaintTargets, h as blockIdentifierForCommand, hn as getDefaultCardVariants, ht as generateMobileOverlayBackdropFilterFix, in as FONT_WEIGHT_MAP, ir as createEventCategoryRef, it as themeButtonVariantClassNames, j as parseGradientPresetId, jn as defaultThemeTokens, jt as resolveTokenOrCssColorValue, k as GRADIENT_PRESET_IDS, kn as onColorTokenName, kt as resolveOnColorForThemeToken, l as createDryRunReport, ln as ACCORDION_SPACING_MAP, lr as createPageRef, lt as expandPalette, m as getSiteCommandMetadata, mn as generateCardCoreCss, mr as __toESM, mt as generateHeaderScrollTreatmentStyles, nn as NAV_LINK_PADDING_X_MAP, nr as createCommandRef, nt as themeButtonExtraClassesWithTrailingIcon, o as venueManagementFields, on as INPUT_SPACING_MAP, or as createFooterRef, ot as compileButtonColourStates, p as evaluateSiteCommandExposure, pn as ICON_SIZE_PX, pr as slugPart, pt as boxBackgroundSchema, q as contextualButtonSurfaceResetStyles, qn as mediaTypeDiscriminants, qt as tileTreatmentValues, rn as NAV_LINK_TEXT_SIZE_MAP, rr as createContentEntryRef, rt as themeButtonSizeClassName, s as sortRecord, sn as LABEL_FONT_SIZE_MAP, sr as createFormRef, st as formatButtonColourContractError, t as classifySdkEventTiming, tn as NAV_LINK_PADDING_MAP, tr as createBlocksRef, tt as parseThemeButtonVariantId, u as parseAndPlanCommandBatchForTypes, un as ACCORDION_TRIGGER_FONT_SCALE, ur as createSiteCommandSiteRef, ut as getTextColorForBackground, v as pageIdentifierForBlockCommand, vn as backgroundColorStyle, vt as BREAKPOINT_LG, w as mergeHeaderNavTypographyPreference, wn as textColorValue, wt as MQ_SM_DOWN, x as hasConfiguredBookingVertical, xn as mergeStyles, xt as BREAKPOINT_XL, y as pageIdentifierForCommand, yn as borderColorStyle, yt as BREAKPOINT_MD, z as pickShadeTowardBackground, zn as surfaceForegroundStyle, zt as eventListTreatmentValues } from "../sdk-event-upsert-BNxbxaA0.mjs";
3
- import { $ as formatEventPricing, A as parseFooterCompositionV1, B as formatBookingTime, C as parseMailingListContentVersionId, D as DEFAULT_MAILING_LIST_SUCCESS_MESSAGE, E as planMailingListChoicePresentation, Et as unsafeAsMediaAssetId, F as normalizeLocationAddressText, G as getLocalScheduleTime, H as formatLocalScheduleTime, Ht as ok, I as toLocationCoordinates, It as unsafeAsStaffMemberId, J as toScheduledBookingDateRange, L as buildBookingUtcDateWindow, N as assertAllowedPublicUrl, O as parseMailingListProviderScope, Q as formatBookingPriceDisplay, R as formatBookingDate, S as parseMailingListBlockId, T as parseMailingListRuntimeContext, U as getBookingLocalDateKey, V as formatLocalScheduleDate, Vt as err, W as getLocalScheduleDate, Y as toScheduledBookingDateTime, Z as parseEventPageSurfaceSubject, _ as createSecret, at as resolvePricingOptionsPriceDisplay, bt as unsafeAsDemoCapability, c as DEFAULT_EVENT_TYPE_PRESETS, ct as formatCurrencyAmount, d as SURFACE_EVENT_PRESET_VALUES, dt as parseEventSeriesId, et as isPublicEventPricingOptionDisplayable, f as WHOLE_SERIES_EVENT_PRESET_VALUES, ft as parseMediaAssetId, g as resolveWholeSeriesBookingPhase, gt as unsafeAsAppointmentResourceId, h as orderedLiveRunSessions, ht as parseUserId, it as resolveExactOrFreePriceDisplay, j as parseMailingListPlacementTargeting, kt as unsafeAsPositiveByteCount, l as EVENT_PRESET_VALUES, lt as assertNever, m as CANCELLED_SESSION_STATUS, mt as parseSiteId, n as NAVIGATION_LOCATIONS, nt as publicEventPricingDisplay, p as publicReadFreshnessSeconds, pt as parsePageId, q as toBookingTimeZone, r as areConfiguredBookingVerticalConfigsEqual, rt as publicEventPricingType, s as resolveSiteVerticalBookingConfig, t as resolveEventCatalogue, tt as publicEventPricingCurrency, ut as parseApiKeyId, v as exposeSecret, w as parseMailingListSignedSourceToken, x as buildMailingListSignupBlockSourceRef, y as parseManagementKey, z as formatBookingDateParts } from "../src-DbmhanBI.mjs";
4
- import { $ as sdkUpsertEventResponseSchema, A as resolveMediaTransformForAspect, B as asRecord$1, C as walkAndCollectImageAttrs, D as normalizeWidthCandidates, E as rotateNormalizedPoint, F as buildMediaIdentifierFromFilename, G as parseSafeDownloadFilename, H as FILE_SIZE_LIMITS, I as parseMediaIdentifier, J as createHttpClient, K as ManagementApiError, L as deriveVideoSlotPolicy, M as getDirectImageUrlForSiteScopedPath, N as getImageUrl, O as isExplicitRectCrop, P as isSiteScopedDirectImageStoragePath, Q as sdkListEventsResponseSchema, R as resolveVideoSelectionIntentForMediaField, S as buildRenderableMediaAssetIndex, T as looksLikeUuid$1, U as classifyKnownMimeType, V as parseRelativeStoragePath, W as managementMediaLibraryUploadLimitForMimeType, X as sdkConfigureBookingVerticalResponseSchema, Y as is404Error, Z as sdkGetEventResponseSchema, _ as buildSettingsUpdateInput, _t as sdkConfiguredBookingVerticalConfigSchema, a as prepareLocalMediaForPush, at as externalLinkSchema$1, b as isMediaValue, c as writeBinaryFileAtomic, ct as internalRouteOnlyLinkSchema, d as normalizeMediaValueToIdentifierOnly, dt as linkSchema, et as sdkSettingsUpdateSchema, f as walkLocalContentMedia, ft as pageLinkSchema$1, g as SETTINGS_FILE_FIELDS, gt as sdkReorderResponseSchema, h as extractMediaIdentifiers, ht as sdkListEventOccurrencesResponseSchema, i as applyLocalMediaPush, it as entryLinkSchema$1, j as getDirectImageUrlForRelativePath, k as parseAspectRatioValue, l as sha256, lt as isInternalResolvedLinkValue, m as downloadMedia, mt as sdkEventOccurrenceResponseSchema, nt as RESOLVED_INTERNAL_LINK_ENTITY_TYPES, o as admitBinaryWriteDestination, ot as internalLinkSchema, p as buildStorageUrl, pt as sdkDeleteEventOccurrenceResponseSchema, q as buildRequestInfo, r as syncLocalMediaForPull, rt as customLinkSchema$1, s as readStableBinaryFile, st as internalResolvedLinkSchema, t as revalidateLocalMediaPushPlan, tt as linkPayloadSchema, u as normalizeMediaIdentifierInput, ut as isResolvedInternalLinkEntityType, v as hasOwnSettingsField, w as deleteEnvSpecificKeys, x as attachRenderableMediaAssetFields, y as writeMediaManifest, z as getMimeType } from "../local-sync-DF-2DL_D.mjs";
5
- import { n as CliSdkPeerError, r as CLI_VERSION, t as loadConfigFile } from "../load-config-Cf5uPEAn.mjs";
6
- import { a as RichTextDocSchema, c as isRecord$17, i as findUnfilledSentinels, n as buildEntryScaffold, o as isRichTextDoc, r as validateEntryInstance, s as isObjectRecord, t as eventUpsertInputSchema } from "../eventUpsertInputSchema-CqHIJp9H.mjs";
7
- import { a as prepareScaffoldExecution, c as LOCAL_PACKAGE_VENDOR_DIR, d as loadGeneratedPackageRanges, i as preflightScaffoldTargets, n as createScaffoldCompletion, s as deriveScaffoldLocalManifest, t as deriveScaffoldPlan } from "../plan-By0bjnd3.mjs";
2
+ import { $ as resolveContextualButtonRole, $n as footerModeSchema, $t as getComponentBorderWidthPx, A as boundaryLayout, An as surfaceToneFromForegroundToken, At as headerVariantValues, B as require_jsx_runtime, Bn as oklchToHexGamut, Bt as defineBlockDesignCapability, C as mergeHeaderNavTypographyPreference, Cn as borderColorStyle, Ct as BREAKPOINT_SM, D as GRADIENT_PRESET_IDS, Dn as textColorStyle, Dt as MQ_SM_DOWN, E as gradientToCss, En as rgbColorValue, Et as MQ_MD_DOWN, F as transitionCatalogueEntries, Fn as defaultThemeTokens, Ft as pageDesignEditorBackgroundOverrideOwner, G as PUBLIC_BUTTON_ROLES, Gn as generateShadowValue$1, Gt as featureCardCompositionTreatmentValues, H as pickShadeTowardBackground, Hn as surfaceForegroundDeclarations, Ht as defineNoBlockDesignCapability, I as backgroundAppearanceColorTokenNames, In as ORDINARY_TEXT_MIN_CONTRAST_RATIO, It as stripStaleTransitionStyleGroup, J as resolvedButtonColourDeclarations, Jn as normalizeCardRole, Jt as makeBoundaryId, K as generateButtonCss, Kn as cardRoleSchema, Kt as headingDecorationColorTokenValues, L as backgroundAppearanceQuickColorTokenNames, Ln as contrastRatio, M as sectionFactsForContent, Mn as isOnColorBackgroundCandidateToken, Mt as resolveOnColorForThemeToken, N as boundaryRuntimeMetricsForDecorativeTransition, Nn as onColorTokenName, Nt as resolveThemeColorReferenceValue, O as getGradientPreset, On as textColorValue, Ot as MQ_SM_UP, P as anglePolygonCss, Pn as buildThemeTokens, Pt as resolveTokenOrCssColorValue, Q as deriveButtonSurfaceContext, Qn as persistableMediaSchema, Qt as visibleHeadingDecorationFamilyValues, R as parseBackgroundAppearanceColorToken, Rn as featuresFromHex, Rt as asDesignBlockId, Sn as backgroundColorStyle, St as BREAKPOINT_MD, T as getCuratedSiteStyle, Tn as mergeStyles, Tt as MQ_LG_UP, U as generateCssVars, Un as surfaceForegroundStyle, Ut as eventListTreatmentValues, V as require_react, Vn as DEFAULT_SURFACE_TEXT_COLOR, Vt as defineFrameCapability, Wn as surfaceForegroundValue, Wt as eyebrowTreatmentValues, X as contextualButtonSurfaceResetStyles, Xn as mediaSchema, Xt as testimonialsCompositionTreatmentValues, Y as resolvedButtonFocusDeclarations, Yn as mediaIdentifierSchema, Yt as offeringsPreviewCompositionTreatmentValues, Z as contextualButtonSurfaceStyles, Zn as mediaTypeDiscriminants, Zt as tileTreatmentValues, _ as entryIdentifierForCommand, _n as ICON_SIZE_PX, _r as slugPart, _t as generateHeaderScrollTreatmentStyles, a as classifySdkEventBookingOffer, an as NAV_LINK_PADDING_MAP, ar as createBlocksRef, at as themeButtonExtraClassesWithTrailingIcon, b as pagePathForCommand, bn as generateCardCss, bt as renderHeaderPaintTargetRules, c as stableStringify, cn as FONT_WEIGHT_MAP, cr as createEventCategoryRef, ct as parseThemeForPersistence, d as mapEventCommandContent, dn as LABEL_FONT_SIZE_MAP, dr as createFormRef, dt as generateShadowValue, en as getComponentShadowVar, er as VARIANT_ROLES, et as generateDefaultButtonSystem, f as evaluateSiteCommandBatchExposure, fn as ACCORDION_OPEN_ACCENT_THICKNESS, fr as createNavigationRef, ft as expandPalette, g as blockOrderIdentifiersForCommand, gn as ICON_CONFIG, gr as refSlug, gt as boxBackgroundSchema, h as blockIdentifierForCommand, hn as ACCORDION_TRIGGER_LETTER_SPACING, hr as createVenueRef, ht as boxBackgroundOverlaySchema, in as NAV_LINK_LETTER_SPACING_MAP, ir as createBlockRef, it as parseThemeButtonVariantId, j as composeBoundary, jn as resolveSurfaceForeground, jt as isCssColorLiteral, k as parseGradientPresetId, kn as selectLogoVariant, kt as MQ_TABLET_ONLY, l as createDryRunReport, ln as INPUT_FONT_SIZE_MAP, lr as createEventSeriesRef, lt as compileButtonColourStates, m as getSiteCommandMetadata, mn as ACCORDION_TRIGGER_FONT_SCALE, mr as createSiteCommandSiteRef, mt as themeSchema, nn as parseCustomCssRules, nr as unsafeAsOpaqueButtonColourToken, nt as generateBaseInteractiveCSS, o as venueManagementFields, on as NAV_LINK_PADDING_X_MAP, or as createCommandRef, ot as themeButtonSizeClassName, p as evaluateSiteCommandExposure, pn as ACCORDION_SPACING_MAP, pr as createPageRef, pt as getTextColorForBackground, q as RESOLVED_BUTTON_DISABLED_DECLARATIONS, qn as cardVariantRoleValues, qt as introCompositionTreatmentValues, rn as NAV_LINK_BORDER_RADIUS_MAP, rr as parseBlockColorToken, rt as generateInteractiveChildInheritanceCSS, s as sortRecord, sn as NAV_LINK_TEXT_SIZE_MAP, sr as createContentEntryRef, st as themeButtonVariantClassNames, t as classifySdkEventTiming, tn as parseBlockCustomCssRules, tr as buttonVariantIdSchema, tt as generateEffectsCSS, u as parseAndPlanCommandBatchForTypes, un as INPUT_SPACING_MAP, ur as createFooterRef, ut as formatButtonColourContractError, v as pageIdentifierForBlockCommand, vn as generateCardCoreCss, vr as __toESM, vt as generateMobileOverlayBackdropFilterFix, w as readHeaderNavFontSizePreference, wn as cssVarValue, wt as BREAKPOINT_XL, x as hasConfiguredBookingVertical, xn as TokenResolver, xt as BREAKPOINT_LG, y as pageIdentifierForCommand, yn as getDefaultCardVariants, yt as getHeaderPaintTargets, z as defineBlockDesignMetadata, zn as hexToRgb, zt as assertNever$4 } from "../sdk-event-upsert-DhdlT2ko.mjs";
3
+ import { $ as formatBookingPriceDisplay, A as parseFooterCompositionV1, At as unsafeAsPositiveByteCount, B as formatBookingDateParts, C as parseMailingListContentVersionId, D as DEFAULT_MAILING_LIST_SUCCESS_MESSAGE, Dt as unsafeAsMediaAssetId, E as planMailingListChoicePresentation, F as normalizeLocationAddressText, G as getLocalScheduleDate, H as formatLocalScheduleDate, Ht as err, I as toLocationCoordinates, J as toBookingTimeZone, K as getLocalScheduleTime, L as selectCategoryView, Lt as unsafeAsStaffMemberId, N as assertAllowedPublicUrl, O as parseMailingListProviderScope, Q as parseEventPageSurfaceSubject, R as buildBookingUtcDateWindow, S as parseMailingListBlockId, T as parseMailingListRuntimeContext, U as formatLocalScheduleTime, Ut as ok, V as formatBookingTime, W as getBookingLocalDateKey, X as toScheduledBookingDateTime, Y as toScheduledBookingDateRange, _ as createSecret, _t as unsafeAsAppointmentResourceId, at as resolveExactOrFreePriceDisplay, c as DEFAULT_EVENT_TYPE_PRESETS, d as SURFACE_EVENT_PRESET_VALUES, dt as parseApiKeyId, et as formatEventPricing, f as WHOLE_SERIES_EVENT_PRESET_VALUES, ft as parseEventSeriesId, g as resolveWholeSeriesBookingPhase, gt as parseUserId, h as orderedLiveRunSessions, ht as parseSiteId, it as publicEventPricingType, j as parseMailingListPlacementTargeting, l as EVENT_PRESET_VALUES, lt as formatCurrencyAmount, m as CANCELLED_SESSION_STATUS, mt as parsePageId, n as NAVIGATION_LOCATIONS, nt as publicEventPricingCurrency, ot as resolvePricingOptionsPriceDisplay, p as publicReadFreshnessSeconds, pt as parseMediaAssetId, r as areConfiguredBookingVerticalConfigsEqual, rt as publicEventPricingDisplay, s as resolveSiteVerticalBookingConfig, t as resolveEventCatalogue, tt as isPublicEventPricingOptionDisplayable, ut as assertNever, v as exposeSecret, w as parseMailingListSignedSourceToken, x as buildMailingListSignupBlockSourceRef, xt as unsafeAsDemoCapability, y as parseManagementKey, z as formatBookingDate } from "../src-DIAVNRwo.mjs";
4
+ import { $ as sdkUpsertEventResponseSchema, A as resolveMediaTransformForAspect, B as asRecord$1, C as walkAndCollectImageAttrs, D as normalizeWidthCandidates, E as rotateNormalizedPoint, F as buildMediaIdentifierFromFilename, G as parseSafeDownloadFilename, H as FILE_SIZE_LIMITS, I as parseMediaIdentifier, J as createHttpClient, K as ManagementApiError, L as deriveVideoSlotPolicy, M as getDirectImageUrlForSiteScopedPath, N as getImageUrl, O as isExplicitRectCrop, P as isSiteScopedDirectImageStoragePath, Q as sdkListEventsResponseSchema, R as resolveVideoSelectionIntentForMediaField, S as buildRenderableMediaAssetIndex, T as looksLikeUuid$1, U as classifyKnownMimeType, V as parseRelativeStoragePath, W as managementMediaLibraryUploadLimitForMimeType, X as sdkConfigureBookingVerticalResponseSchema, Y as is404Error, Z as sdkGetEventResponseSchema, _ as buildSettingsUpdateInput, _t as sdkConfiguredBookingVerticalConfigSchema, a as prepareLocalMediaForPush, at as externalLinkSchema$1, b as isMediaValue, c as writeBinaryFileAtomic, ct as internalRouteOnlyLinkSchema, d as normalizeMediaValueToIdentifierOnly, dt as linkSchema, et as sdkSettingsUpdateSchema, f as walkLocalContentMedia, ft as pageLinkSchema$1, g as SETTINGS_FILE_FIELDS, gt as sdkReorderResponseSchema, h as extractMediaIdentifiers, ht as sdkListEventOccurrencesResponseSchema, i as applyLocalMediaPush, it as entryLinkSchema$1, j as getDirectImageUrlForRelativePath, k as parseAspectRatioValue, l as sha256, lt as isInternalResolvedLinkValue, m as downloadMedia, mt as sdkEventOccurrenceResponseSchema, nt as RESOLVED_INTERNAL_LINK_ENTITY_TYPES, o as admitBinaryWriteDestination, ot as internalLinkSchema, p as buildStorageUrl, pt as sdkDeleteEventOccurrenceResponseSchema, q as buildRequestInfo, r as syncLocalMediaForPull, rt as customLinkSchema$1, s as readStableBinaryFile, st as internalResolvedLinkSchema, t as revalidateLocalMediaPushPlan, tt as linkPayloadSchema, u as normalizeMediaIdentifierInput, ut as isResolvedInternalLinkEntityType, v as hasOwnSettingsField, w as deleteEnvSpecificKeys, x as attachRenderableMediaAssetFields, y as writeMediaManifest, z as getMimeType } from "../local-sync-3ZG1khGb.mjs";
5
+ import { n as CliSdkPeerError, r as CLI_VERSION, t as loadConfigFile } from "../load-config-Bzc6Qo24.mjs";
6
+ import { a as RichTextDocSchema, c as isRecord$17, i as findUnfilledSentinels, n as buildEntryScaffold, o as isRichTextDoc, r as validateEntryInstance, s as isObjectRecord, t as eventUpsertInputSchema } from "../eventUpsertInputSchema-yErCHdr6.mjs";
7
+ import { a as prepareScaffoldExecution, c as LOCAL_PACKAGE_VENDOR_DIR, d as loadGeneratedPackageRanges, i as preflightScaffoldTargets, n as createScaffoldCompletion, s as deriveScaffoldLocalManifest, t as deriveScaffoldPlan } from "../plan-C1Frmu6F.mjs";
8
8
  import { config } from "dotenv";
9
9
  import { Command, CommanderError } from "commander";
10
10
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -176,16 +176,16 @@ var BlockPatchStaleContentError = class extends ManagementApiError {
176
176
  function isBlockPatchStaleContentError(error$1) {
177
177
  return error$1 instanceof BlockPatchStaleContentError;
178
178
  }
179
- function isRecord$36(value) {
179
+ function isRecord$37(value) {
180
180
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
181
181
  }
182
182
  function extractStaleContentCurrentHash(error$1) {
183
183
  if (error$1.statusCode !== 409) return null;
184
- if (!isRecord$36(error$1.details)) return null;
184
+ if (!isRecord$37(error$1.details)) return null;
185
185
  const fieldErrors = error$1.details.fieldErrors;
186
186
  if (!Array.isArray(fieldErrors)) return null;
187
187
  for (const fieldError of fieldErrors) {
188
- if (isRecord$36(fieldError) && fieldError.field === SDK_PATCH_STALE_CONTENT_FIELD && fieldError.code === SDK_PATCH_STALE_CONTENT_CODE && typeof fieldError.message === "string") {
188
+ if (isRecord$37(fieldError) && fieldError.field === SDK_PATCH_STALE_CONTENT_FIELD && fieldError.code === SDK_PATCH_STALE_CONTENT_CODE && typeof fieldError.message === "string") {
189
189
  return fieldError.message;
190
190
  }
191
191
  }
@@ -2524,6 +2524,77 @@ function parsePublicProductCategorySelector(value) {
2524
2524
  return undefined;
2525
2525
  }
2526
2526
 
2527
+ //#endregion
2528
+ //#region ../api/src/defineEndpointDefinitions.ts
2529
+ /**
2530
+ * Validates extracted endpoint descriptors while preserving each literal path,
2531
+ * method, and parameter contract for the typed URL builders.
2532
+ */
2533
+ function defineEndpointDefinitions(definitions$1) {
2534
+ return definitions$1;
2535
+ }
2536
+
2537
+ //#endregion
2538
+ //#region ../api/src/publicNewsletterEndpointDefinitions.ts
2539
+ const PUBLIC_NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2540
+ subscribeNewsletter: {
2541
+ path: "/public/sites/{siteId}/newsletter/subscribe",
2542
+ method: "POST",
2543
+ tags: ["public-newsletter-{siteId}"],
2544
+ auth: "public",
2545
+ responseKind: "json"
2546
+ },
2547
+ confirmNewsletter: {
2548
+ path: "/public/sites/{siteId}/newsletter/confirm/{token}",
2549
+ method: "GET",
2550
+ tags: ["public-newsletter-{siteId}"],
2551
+ auth: "public",
2552
+ responseKind: "text"
2553
+ },
2554
+ unsubscribeNewsletter: {
2555
+ path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
2556
+ method: "GET",
2557
+ tags: ["public-newsletter-{siteId}"],
2558
+ auth: "public",
2559
+ responseKind: "text"
2560
+ },
2561
+ unsubscribeNewsletterPost: {
2562
+ path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
2563
+ method: "POST",
2564
+ tags: ["public-newsletter-{siteId}"],
2565
+ auth: "public",
2566
+ responseKind: "text"
2567
+ },
2568
+ confirmNewsletterPost: {
2569
+ path: "/public/sites/{siteId}/newsletter/confirm/{token}",
2570
+ method: "POST",
2571
+ tags: ["public-newsletter-{siteId}"],
2572
+ auth: "public",
2573
+ responseKind: "text"
2574
+ },
2575
+ getNewsletterPreferences: {
2576
+ path: "/public/sites/{siteId}/newsletter/preferences/{token}",
2577
+ method: "GET",
2578
+ tags: ["public-newsletter-{siteId}"],
2579
+ auth: "public",
2580
+ responseKind: "text"
2581
+ },
2582
+ saveNewsletterPreferences: {
2583
+ path: "/public/sites/{siteId}/newsletter/preferences/{token}",
2584
+ method: "POST",
2585
+ tags: ["public-newsletter-{siteId}"],
2586
+ auth: "public",
2587
+ responseKind: "text"
2588
+ },
2589
+ getNewsletterArchive: {
2590
+ path: "/public/sites/{siteId}/newsletter/archive/{token}",
2591
+ method: "GET",
2592
+ tags: ["public-newsletter-{siteId}"],
2593
+ auth: "public",
2594
+ responseKind: "text"
2595
+ }
2596
+ });
2597
+
2527
2598
  //#endregion
2528
2599
  //#region ../api/src/apiEndpointTypes.ts
2529
2600
  function allowedDemoMutationPolicy() {
@@ -2783,18 +2854,89 @@ const SDK_ENTRY_READ_ENDPOINTS = {
2783
2854
  };
2784
2855
 
2785
2856
  //#endregion
2786
- //#region ../api/src/defineEndpointDefinitions.ts
2787
- /**
2788
- * Validates extracted endpoint descriptors while preserving each literal path,
2789
- * method, and parameter contract for the typed URL builders.
2790
- */
2791
- function defineEndpointDefinitions(definitions$1) {
2792
- return definitions$1;
2793
- }
2857
+ //#region ../api/src/appointmentServiceCategoryEndpoints.ts
2858
+ const APPOINTMENT_SERVICE_CATEGORY_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2859
+ listGroupedAppointmentServices: {
2860
+ path: "/sites/{siteId}/bookings/services/grouped",
2861
+ method: "GET",
2862
+ auth: "user",
2863
+ responseKind: "json",
2864
+ errors: ["server:internal_error"]
2865
+ },
2866
+ reorderAppointmentServices: {
2867
+ path: "/sites/{siteId}/bookings/services/order",
2868
+ method: "PUT",
2869
+ auth: "user",
2870
+ responseKind: "json",
2871
+ errors: [
2872
+ "validation:invalid_input",
2873
+ "auth:forbidden",
2874
+ "resource:not_found",
2875
+ "resource:conflict",
2876
+ "server:internal_error"
2877
+ ]
2878
+ },
2879
+ listAppointmentServiceCategories: {
2880
+ path: "/sites/{siteId}/bookings/service-categories",
2881
+ method: "GET",
2882
+ auth: "user",
2883
+ responseKind: "json",
2884
+ errors: ["server:internal_error"]
2885
+ },
2886
+ createAppointmentServiceCategory: {
2887
+ path: "/sites/{siteId}/bookings/service-categories",
2888
+ method: "POST",
2889
+ auth: "user",
2890
+ responseKind: "json",
2891
+ errors: [
2892
+ "validation:invalid_input",
2893
+ "auth:forbidden",
2894
+ "resource:conflict",
2895
+ "server:internal_error"
2896
+ ]
2897
+ },
2898
+ updateAppointmentServiceCategory: {
2899
+ path: "/sites/{siteId}/bookings/service-categories/{categoryId}",
2900
+ method: "PUT",
2901
+ auth: "user",
2902
+ responseKind: "json",
2903
+ errors: [
2904
+ "validation:invalid_input",
2905
+ "auth:forbidden",
2906
+ "resource:not_found",
2907
+ "resource:conflict",
2908
+ "server:internal_error"
2909
+ ]
2910
+ },
2911
+ deleteAppointmentServiceCategory: {
2912
+ path: "/sites/{siteId}/bookings/service-categories/{categoryId}",
2913
+ method: "DELETE",
2914
+ auth: "user",
2915
+ responseKind: "json",
2916
+ errors: [
2917
+ "auth:forbidden",
2918
+ "resource:not_found",
2919
+ "server:internal_error"
2920
+ ]
2921
+ },
2922
+ reorderAppointmentServiceCategories: {
2923
+ path: "/sites/{siteId}/bookings/service-categories/order",
2924
+ method: "PUT",
2925
+ auth: "user",
2926
+ responseKind: "json",
2927
+ errors: [
2928
+ "validation:invalid_input",
2929
+ "auth:forbidden",
2930
+ "resource:conflict",
2931
+ "server:internal_error"
2932
+ ]
2933
+ }
2934
+ });
2794
2935
 
2795
2936
  //#endregion
2796
2937
  //#region ../api/src/appointmentEndpointDefinitions.ts
2797
2938
  const APPOINTMENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2939
+ ...APPOINTMENT_SERVICE_CATEGORY_ENDPOINT_DEFINITIONS,
2798
2940
  listAppointmentResources: {
2799
2941
  path: "/sites/{siteId}/bookings/resources",
2800
2942
  method: "GET",
@@ -3945,39 +4087,6 @@ const NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
3945
4087
  auth: "user",
3946
4088
  responseKind: "json",
3947
4089
  demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN
3948
- },
3949
- listNewsletterSendJobs: {
3950
- path: "/sites/{siteId}/newsletter/jobs",
3951
- method: "GET",
3952
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
3953
- auth: "user",
3954
- responseKind: "json"
3955
- },
3956
- retryNewsletterSendJob: {
3957
- path: "/sites/{siteId}/newsletter/jobs/{jobId}/retry",
3958
- method: "POST",
3959
- errors: [
3960
- "validation:invalid_input",
3961
- "resource:not_found",
3962
- "auth:forbidden",
3963
- "server:internal_error"
3964
- ],
3965
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
3966
- auth: "user",
3967
- responseKind: "json"
3968
- },
3969
- cancelNewsletterSendJob: {
3970
- path: "/sites/{siteId}/newsletter/jobs/{jobId}/cancel",
3971
- method: "POST",
3972
- errors: [
3973
- "validation:invalid_input",
3974
- "resource:not_found",
3975
- "auth:forbidden",
3976
- "server:internal_error"
3977
- ],
3978
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
3979
- auth: "user",
3980
- responseKind: "json"
3981
4090
  }
3982
4091
  });
3983
4092
 
@@ -4757,6 +4866,7 @@ const BILLING_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
4757
4866
  //#endregion
4758
4867
  //#region ../api/src/endpoints.ts
4759
4868
  const ENDPOINT_DEFINITIONS = {
4869
+ ...PUBLIC_NEWSLETTER_ENDPOINT_DEFINITIONS,
4760
4870
  ...ACCOUNT_ENDPOINT_DEFINITIONS,
4761
4871
  ...PUBLIC_ONBOARDING_ATTEMPT_ENDPOINT_DEFINITIONS,
4762
4872
  ...PUBLIC_EVENT_ENDPOINT_DEFINITIONS,
@@ -7891,41 +8001,6 @@ const ENDPOINT_DEFINITIONS = {
7891
8001
  "server:internal_error"
7892
8002
  ]
7893
8003
  },
7894
- subscribeNewsletter: {
7895
- path: "/public/sites/{siteId}/newsletter/subscribe",
7896
- method: "POST",
7897
- tags: ["public-newsletter-{siteId}"],
7898
- auth: "public",
7899
- responseKind: "json"
7900
- },
7901
- confirmNewsletter: {
7902
- path: "/public/sites/{siteId}/newsletter/confirm/{token}",
7903
- method: "GET",
7904
- tags: ["public-newsletter-{siteId}"],
7905
- auth: "public",
7906
- responseKind: "text"
7907
- },
7908
- unsubscribeNewsletter: {
7909
- path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
7910
- method: "GET",
7911
- tags: ["public-newsletter-{siteId}"],
7912
- auth: "public",
7913
- responseKind: "text"
7914
- },
7915
- unsubscribeNewsletterPost: {
7916
- path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
7917
- method: "POST",
7918
- tags: ["public-newsletter-{siteId}"],
7919
- auth: "public",
7920
- responseKind: "text"
7921
- },
7922
- getNewsletterArchive: {
7923
- path: "/public/sites/{siteId}/newsletter/archive/{token}",
7924
- method: "GET",
7925
- tags: ["public-newsletter-{siteId}"],
7926
- auth: "public",
7927
- responseKind: "text"
7928
- },
7929
8004
  listSiteContentTypes: {
7930
8005
  path: "/sites/{siteId}/content-types",
7931
8006
  method: "GET",
@@ -10424,6 +10499,12 @@ function matchSurfaceSiteRootHost(host) {
10424
10499
  }
10425
10500
  return matchSurfaceHost(host, SURFACE_SITE_ROOT_HOSTS);
10426
10501
  }
10502
+ function resolveSurfaceSiteHostContext(value) {
10503
+ if (value === null || value === undefined || typeof value === "string" && value.length === 0) return null;
10504
+ const normalizedHost = normalizeHostLike(value instanceof URL ? value.toString() : value);
10505
+ if (normalizedHost === null) return null;
10506
+ return matchSurfaceSiteRootHost(normalizedHost);
10507
+ }
10427
10508
  function resolveSurfaceHostContext(value) {
10428
10509
  if (value === null || value === undefined || typeof value === "string" && value.length === 0) return null;
10429
10510
  const normalizedHost = normalizeHostLike(value instanceof URL ? value.toString() : value);
@@ -10500,7 +10581,7 @@ function extractSurfaceAwareSiteSlug(value) {
10500
10581
  return null;
10501
10582
  }
10502
10583
  const hostname$1 = parsed.hostname.toLowerCase();
10503
- const context = matchSurfaceSiteRootHost(hostname$1);
10584
+ const context = resolveSurfaceSiteHostContext(hostname$1);
10504
10585
  if (context === null) return null;
10505
10586
  if (hostname$1 === context.surfaceRootHost) return null;
10506
10587
  if (!hostname$1.endsWith(`.${context.surfaceRootHost}`)) return null;
@@ -10665,20 +10746,20 @@ function isApiError(result) {
10665
10746
  function isApiSuccess(result) {
10666
10747
  return result.success === true;
10667
10748
  }
10668
- function isRecord$35(value) {
10749
+ function isRecord$36(value) {
10669
10750
  return typeof value === "object" && value !== null && !Array.isArray(value);
10670
10751
  }
10671
10752
  function isFieldError(value) {
10672
- return isRecord$35(value) && typeof value.field === "string" && typeof value.code === "string" && typeof value.message === "string";
10753
+ return isRecord$36(value) && typeof value.field === "string" && typeof value.code === "string" && typeof value.message === "string";
10673
10754
  }
10674
10755
  function fieldErrorsFromUnknown(value) {
10675
10756
  if (!Array.isArray(value)) return null;
10676
10757
  return value.every(isFieldError) ? value : null;
10677
10758
  }
10678
10759
  function extractEnvelopeErrorObject(body) {
10679
- if (!isRecord$35(body)) return null;
10760
+ if (!isRecord$36(body)) return null;
10680
10761
  const error$1 = body.error;
10681
- if (!isRecord$35(error$1)) return null;
10762
+ if (!isRecord$36(error$1)) return null;
10682
10763
  if ("success" in body && body.success !== false) {
10683
10764
  return null;
10684
10765
  }
@@ -11288,7 +11369,7 @@ const DEFAULT_CONFIG_PATH = "./riverbank.config.ts";
11288
11369
  function formatPushConfigActionFailure() {
11289
11370
  return "push-config failed";
11290
11371
  }
11291
- function isRecord$34(value) {
11372
+ function isRecord$35(value) {
11292
11373
  return typeof value === "object" && value !== null;
11293
11374
  }
11294
11375
  function parseErrorDetails(value) {
@@ -11296,10 +11377,10 @@ function parseErrorDetails(value) {
11296
11377
  if (typeof value === "string") {
11297
11378
  return { message: value };
11298
11379
  }
11299
- if (!isRecord$34(value)) return {};
11380
+ if (!isRecord$35(value)) return {};
11300
11381
  const message = typeof value.message === "string" ? value.message : undefined;
11301
11382
  const fieldErrorsRaw = value.fieldErrors;
11302
- const fieldErrors = Array.isArray(fieldErrorsRaw) ? fieldErrorsRaw.filter(isRecord$34).map((d) => ({
11383
+ const fieldErrors = Array.isArray(fieldErrorsRaw) ? fieldErrorsRaw.filter(isRecord$35).map((d) => ({
11303
11384
  field: typeof d.field === "string" ? d.field : "",
11304
11385
  message: typeof d.message === "string" ? d.message : ""
11305
11386
  })).filter((d) => d.field.length > 0 && d.message.length > 0) : undefined;
@@ -11314,7 +11395,7 @@ function formatFieldErrors(fieldErrors) {
11314
11395
  }
11315
11396
  function getDashboardErrorMessage(responseJson, fallback) {
11316
11397
  if (!responseJson) return fallback;
11317
- if (isRecord$34(responseJson) && "error" in responseJson) {
11398
+ if (isRecord$35(responseJson) && "error" in responseJson) {
11318
11399
  const parsed = parseErrorDetails(responseJson.error);
11319
11400
  const msg = parsed.message ?? fallback;
11320
11401
  return msg + formatFieldErrors(parsed.fieldErrors);
@@ -14618,7 +14699,8 @@ const siteOptionSources = [
14618
14699
  "eventCategories",
14619
14700
  "eventSeries",
14620
14701
  "venues",
14621
- "staffMembers"
14702
+ "staffMembers",
14703
+ "appointmentServiceCategories"
14622
14704
  ];
14623
14705
  const mediaDeliverySchema = z.object({
14624
14706
  sizes: z.string().optional(),
@@ -20504,7 +20586,7 @@ const teamMemberFragment = defineFragment({
20504
20586
  //#region ../blocks/src/system/data/linkedContentEntries.ts
20505
20587
  const LINKED_CONTENT_ENTRY_META_KEY = "__rbLinkedContentEntry";
20506
20588
  function coerceLinkedContentEntriesPayload(payload) {
20507
- if (!isRecord$33(payload)) return payload;
20589
+ if (!isRecord$34(payload)) return payload;
20508
20590
  const response = payload;
20509
20591
  if (!Array.isArray(response.entries)) return payload;
20510
20592
  const contentType = parsePublicContentTypeMeta(response.contentType);
@@ -20512,16 +20594,16 @@ function coerceLinkedContentEntriesPayload(payload) {
20512
20594
  return response.entries.map((entry) => enrichLinkedContentEntry(entry, contentType));
20513
20595
  }
20514
20596
  function getLinkedContentEntryMeta(value) {
20515
- if (!isRecord$33(value)) return null;
20597
+ if (!isRecord$34(value)) return null;
20516
20598
  const candidate = value[LINKED_CONTENT_ENTRY_META_KEY];
20517
20599
  return isLinkedContentEntryMeta(candidate) ? candidate : null;
20518
20600
  }
20519
20601
  function buildLinkedContentEditTarget(input) {
20520
20602
  const { entry, sourceBlockId, sourceBlockKind, itemIndex } = input;
20521
- if (!isRecord$33(entry)) return null;
20603
+ if (!isRecord$34(entry)) return null;
20522
20604
  const meta = getLinkedContentEntryMeta(entry);
20523
20605
  if (!meta || meta.support.kind !== "supported") return null;
20524
- const content = isRecord$33(entry.content) ? entry.content : {};
20606
+ const content = isRecord$34(entry.content) ? entry.content : {};
20525
20607
  return {
20526
20608
  kind: "linked-content-entry",
20527
20609
  entryId: meta.entryId,
@@ -20595,12 +20677,12 @@ function resolveLinkedEntryInlineFieldEligibility(input) {
20595
20677
  };
20596
20678
  }
20597
20679
  function enrichLinkedContentEntry(entry, contentType) {
20598
- if (!isRecord$33(entry)) return entry;
20680
+ if (!isRecord$34(entry)) return entry;
20599
20681
  const entryId = typeof entry.id === "string" ? entry.id : null;
20600
20682
  if (!entryId) return entry;
20601
20683
  const title = typeof entry.title === "string" && entry.title.trim().length > 0 ? entry.title : entryId;
20602
20684
  const optimisticHash = typeof entry.optimisticHash === "string" && entry.optimisticHash.length > 0 ? entry.optimisticHash : null;
20603
- const content = isRecord$33(entry.content) ? entry.content : {};
20685
+ const content = isRecord$34(entry.content) ? entry.content : {};
20604
20686
  const meta = {
20605
20687
  kind: "linked-content-entry",
20606
20688
  entryId,
@@ -20709,7 +20791,7 @@ function capitalize(value) {
20709
20791
  return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
20710
20792
  }
20711
20793
  function parsePublicContentTypeMeta(value) {
20712
- if (!isRecord$33(value)) return null;
20794
+ if (!isRecord$34(value)) return null;
20713
20795
  if (typeof value.key !== "string" || value.key.trim().length === 0) return null;
20714
20796
  return {
20715
20797
  key: value.key,
@@ -20719,7 +20801,7 @@ function parsePublicContentTypeMeta(value) {
20719
20801
  };
20720
20802
  }
20721
20803
  function parsePublicContentTypeLinkedEntryEditing(value) {
20722
- if (!isRecord$33(value) || typeof value.kind !== "string") return null;
20804
+ if (!isRecord$34(value) || typeof value.kind !== "string") return null;
20723
20805
  switch (value.kind) {
20724
20806
  case "generic": return { kind: "generic" };
20725
20807
  case "unsupported-domain-backed": return {
@@ -20730,7 +20812,7 @@ function parsePublicContentTypeLinkedEntryEditing(value) {
20730
20812
  }
20731
20813
  }
20732
20814
  function parsePublicContentTypeFieldMeta(value) {
20733
- if (!isRecord$33(value)) return [];
20815
+ if (!isRecord$34(value)) return [];
20734
20816
  if (typeof value.id !== "string" || typeof value.type !== "string") return [];
20735
20817
  return [{
20736
20818
  id: value.id,
@@ -20745,14 +20827,14 @@ function parseStringArray(value) {
20745
20827
  return value.filter((item) => typeof item === "string");
20746
20828
  }
20747
20829
  function isLinkedContentEntryMeta(value) {
20748
- if (!isRecord$33(value)) return false;
20830
+ if (!isRecord$34(value)) return false;
20749
20831
  if (value.kind !== "linked-content-entry") return false;
20750
20832
  if (typeof value.entryId !== "string") return false;
20751
20833
  if (typeof value.contentTypeKey !== "string") return false;
20752
20834
  if (typeof value.contentTypeLabel !== "string") return false;
20753
20835
  if (typeof value.title !== "string") return false;
20754
20836
  if (!(value.optimisticHash === null || typeof value.optimisticHash === "string")) return false;
20755
- if (!isRecord$33(value.contentType)) return false;
20837
+ if (!isRecord$34(value.contentType)) return false;
20756
20838
  if (!isLinkedEntryFieldSourceArray(value.fieldSources)) return false;
20757
20839
  return isLinkedContentEditSupport(value.support);
20758
20840
  }
@@ -20760,7 +20842,7 @@ function isLinkedEntryFieldSourceArray(value) {
20760
20842
  return Array.isArray(value) && value.every(isLinkedEntryFieldSource);
20761
20843
  }
20762
20844
  function isLinkedEntryFieldSource(value) {
20763
- if (!isRecord$33(value)) return false;
20845
+ if (!isRecord$34(value)) return false;
20764
20846
  if (typeof value.displayPath !== "string") return false;
20765
20847
  switch (value.kind) {
20766
20848
  case "content-field": return typeof value.fieldPath === "string" && (value.fieldType === "text" || value.fieldType === "richText");
@@ -20781,7 +20863,7 @@ function isLinkedEntryInlineFieldReadOnlyReason(value) {
20781
20863
  }
20782
20864
  }
20783
20865
  function isLinkedContentEditSupport(value) {
20784
- if (!isRecord$33(value) || typeof value.kind !== "string") return false;
20866
+ if (!isRecord$34(value) || typeof value.kind !== "string") return false;
20785
20867
  switch (value.kind) {
20786
20868
  case "supported": return true;
20787
20869
  case "unsupported-domain-backed":
@@ -20790,7 +20872,7 @@ function isLinkedContentEditSupport(value) {
20790
20872
  default: return false;
20791
20873
  }
20792
20874
  }
20793
- function isRecord$33(value) {
20875
+ function isRecord$34(value) {
20794
20876
  return typeof value === "object" && value !== null && !Array.isArray(value);
20795
20877
  }
20796
20878
  function describeLinkedContentEditSupport(support) {
@@ -26324,11 +26406,11 @@ function assertSafePathSegments(segments) {
26324
26406
  }
26325
26407
  }
26326
26408
  }
26327
- function isRecord$32(value) {
26409
+ function isRecord$33(value) {
26328
26410
  return typeof value === "object" && value !== null && !Array.isArray(value);
26329
26411
  }
26330
26412
  function isPathContainer(value) {
26331
- return Array.isArray(value) || isRecord$32(value);
26413
+ return Array.isArray(value) || isRecord$33(value);
26332
26414
  }
26333
26415
  function createPathContainer(nextIsIndex) {
26334
26416
  return nextIsIndex ? [] : {};
@@ -28188,7 +28270,7 @@ function parseApprovedIconExampleSetId(value) {
28188
28270
  //#endregion
28189
28271
  //#region ../blocks/src/system/icons/value.ts
28190
28272
  const NO_BLOCK_ICON_VALUE = { kind: "none" };
28191
- function isRecord$31(value) {
28273
+ function isRecord$32(value) {
28192
28274
  return typeof value === "object" && value !== null && !Array.isArray(value);
28193
28275
  }
28194
28276
  function parseBlockIconValue(value) {
@@ -28198,7 +28280,7 @@ function parseBlockIconValue(value) {
28198
28280
  iconId: value
28199
28281
  };
28200
28282
  }
28201
- if (!isRecord$31(value)) {
28283
+ if (!isRecord$32(value)) {
28202
28284
  return NO_BLOCK_ICON_VALUE;
28203
28285
  }
28204
28286
  switch (value.kind) {
@@ -28291,7 +28373,7 @@ function buildGeneratedBlockIconRenderAssetLookupFromUnknown(value) {
28291
28373
  }
28292
28374
  const inputs = [];
28293
28375
  for (const [index, item] of value.entries()) {
28294
- if (!isRecord$31(item)) {
28376
+ if (!isRecord$32(item)) {
28295
28377
  return {
28296
28378
  kind: "invalid_asset_entry",
28297
28379
  index,
@@ -28359,7 +28441,7 @@ function resolveBlockIconRenderValue(value, generatedAssets = new Map()) {
28359
28441
  }
28360
28442
  }
28361
28443
  function getGeneratedBlockIconStyleState(value, currentStyleGuideVersionId) {
28362
- if (isRecord$31(value) && value.kind === "generated" && parseBlockIconValue(value).kind !== "generated") {
28444
+ if (isRecord$32(value) && value.kind === "generated" && parseBlockIconValue(value).kind !== "generated") {
28363
28445
  return { kind: "invalid_generated" };
28364
28446
  }
28365
28447
  const parsed = parseBlockIconValue(value);
@@ -28445,11 +28527,11 @@ function resolveFeatureCardGeneratedIconSizePx(value) {
28445
28527
  const parsed = parseFeatureCardGeneratedIconSizePx(value);
28446
28528
  return parsed.ok ? parsed.value : unsafeAsFeatureCardGeneratedIconSizePx(FEATURE_CARD_GENERATED_ICON_SIZE_PX.default);
28447
28529
  }
28448
- function isRecord$30(value) {
28530
+ function isRecord$31(value) {
28449
28531
  return typeof value === "object" && value !== null && !Array.isArray(value);
28450
28532
  }
28451
28533
  function toPlainTextFromTipTap(value) {
28452
- if (!isRecord$30(value)) return "";
28534
+ if (!isRecord$31(value)) return "";
28453
28535
  if (typeof value.text === "string") return value.text;
28454
28536
  const content = value.content;
28455
28537
  if (!Array.isArray(content)) return "";
@@ -28506,7 +28588,7 @@ function suggestSemanticIconForFeatureCard(card) {
28506
28588
 
28507
28589
  //#endregion
28508
28590
  //#region ../blocks/src/system/icons/generatedAssets.ts
28509
- function isRecord$29(value) {
28591
+ function isRecord$30(value) {
28510
28592
  return typeof value === "object" && value !== null && !Array.isArray(value);
28511
28593
  }
28512
28594
  function generatedIconAssetRefKey(ref) {
@@ -28516,11 +28598,11 @@ function collectGeneratedIconAssetRefs(page) {
28516
28598
  const blocks = [];
28517
28599
  for (const block of page.blocks) {
28518
28600
  if (block.kind !== "block.feature-cards" || typeof block.id !== "string") continue;
28519
- const content = isRecord$29(block.draftContent) ? block.draftContent : isRecord$29(block.content) ? block.content : null;
28601
+ const content = isRecord$30(block.draftContent) ? block.draftContent : isRecord$30(block.content) ? block.content : null;
28520
28602
  const cards = Array.isArray(content?.cards) ? content.cards : [];
28521
28603
  const refs = new Map();
28522
28604
  for (const card of cards) {
28523
- if (!isRecord$29(card)) continue;
28605
+ if (!isRecord$30(card)) continue;
28524
28606
  const icon$1 = parseBlockIconValue(card.icon);
28525
28607
  if (icon$1.kind !== "generated") continue;
28526
28608
  const ref = {
@@ -29623,18 +29705,43 @@ const publicAppointmentServiceSchema = z.object({
29623
29705
  pricing: publicPricingSchema.optional(),
29624
29706
  priceAmount: z.number().nullable(),
29625
29707
  priceCurrency: z.string().nullable(),
29626
- location: publicLocationSchema
29708
+ location: publicLocationSchema,
29709
+ categoryId: z.string().nullable().optional().transform((value) => value ?? null)
29710
+ });
29711
+ const publicServiceCategorySchema = z.object({
29712
+ id: z.string().min(1),
29713
+ name: z.string(),
29714
+ description: z.string().nullable().optional().transform((value) => value ?? null)
29627
29715
  });
29628
- const publicAppointmentServicesSchema = z.array(publicAppointmentServiceSchema);
29716
+ /**
29717
+ * The loader hands over the catalogue `{ services, categories }` (issue 1403).
29718
+ * A bare services array is still accepted and read as a site with no
29719
+ * categories, so callers from before categories keep the flat list.
29720
+ */
29721
+ const publicAppointmentServicesSchema = z.union([z.array(publicAppointmentServiceSchema).transform((services) => ({
29722
+ services,
29723
+ categories: []
29724
+ })), z.object({
29725
+ services: z.array(publicAppointmentServiceSchema),
29726
+ categories: z.array(publicServiceCategorySchema)
29727
+ })]);
29629
29728
  const EMPTY_MESSAGE = "No appointments are available right now.";
29630
29729
  const ERROR_MESSAGE = "Appointments are temporarily unavailable. Please try again shortly.";
29730
+ const OTHER_GROUP_KEY = "other";
29731
+ const OTHER_GROUP_HEADING = "Other";
29631
29732
  function parseServices(value) {
29632
29733
  const result = publicAppointmentServicesSchema.safeParse(value);
29633
29734
  return result.success ? {
29634
29735
  kind: "valid",
29635
- services: result.data
29736
+ services: result.data.services,
29737
+ categories: result.data.categories
29636
29738
  } : { kind: "invalid" };
29637
29739
  }
29740
+ function readCategoryFilter(value) {
29741
+ if (typeof value !== "string") return null;
29742
+ const trimmed = value.trim();
29743
+ return trimmed.length > 0 ? trimmed : null;
29744
+ }
29638
29745
  function formatAppointmentDuration(durationMinutes) {
29639
29746
  return `${durationMinutes} min`;
29640
29747
  }
@@ -29708,8 +29815,44 @@ function buildRows(services, pathname, servicePathPrefix) {
29708
29815
  detailsHref: servicePathPrefix && service.slug ? `${servicePathPrefix.replace(/\/$/, "")}/${encodeURIComponent(service.slug)}` : null
29709
29816
  }));
29710
29817
  }
29818
+ const EMPTY_VIEW = {
29819
+ kind: "empty",
29820
+ message: EMPTY_MESSAGE
29821
+ };
29822
+ function categoryGroup(category, services, toRows) {
29823
+ return {
29824
+ key: category.id,
29825
+ heading: category.name,
29826
+ intro: category.description,
29827
+ rows: toRows(services)
29828
+ };
29829
+ }
29830
+ function viewFromLayout(layout, toRows) {
29831
+ switch (layout.kind) {
29832
+ case "uncategorized-site": return layout.services.length === 0 ? EMPTY_VIEW : {
29833
+ kind: "items",
29834
+ rows: toRows(layout.services)
29835
+ };
29836
+ case "categorized-site": {
29837
+ const groups = layout.groups.map((group) => categoryGroup(group.category, group.services, toRows));
29838
+ const other = layout.other.length === 0 ? [] : [{
29839
+ key: OTHER_GROUP_KEY,
29840
+ heading: OTHER_GROUP_HEADING,
29841
+ intro: null,
29842
+ rows: toRows(layout.other)
29843
+ }];
29844
+ const allGroups = [...groups, ...other];
29845
+ return allGroups.length === 0 ? EMPTY_VIEW : {
29846
+ kind: "groups",
29847
+ groups: allGroups
29848
+ };
29849
+ }
29850
+ default: return assertNever(layout);
29851
+ }
29852
+ }
29711
29853
  function buildAppointmentServicesView(input) {
29712
- if (!input.pathname) {
29854
+ const pathname = input.pathname;
29855
+ if (!pathname) {
29713
29856
  return {
29714
29857
  kind: "error",
29715
29858
  message: ERROR_MESSAGE
@@ -29722,27 +29865,101 @@ function buildAppointmentServicesView(input) {
29722
29865
  message: ERROR_MESSAGE
29723
29866
  };
29724
29867
  }
29725
- if (parsed.services.length === 0) {
29726
- return {
29727
- kind: "empty",
29728
- message: EMPTY_MESSAGE
29868
+ const toRows = (services) => buildRows(services, pathname, input.servicePathPrefix ?? null);
29869
+ const view = selectCategoryView(parsed.categories, parsed.services, readCategoryFilter(input.categoryId));
29870
+ switch (view.kind) {
29871
+ case "single": return view.group.services.length === 0 ? EMPTY_VIEW : {
29872
+ kind: "groups",
29873
+ groups: [categoryGroup(view.group.category, view.group.services, toRows)]
29729
29874
  };
29875
+ case "all":
29876
+ case "fallback-all": return viewFromLayout(view.layout, toRows);
29877
+ default: return assertNever(view);
29730
29878
  }
29731
- return {
29732
- kind: "items",
29733
- rows: buildRows(parsed.services, input.pathname, input.servicePathPrefix ?? null)
29734
- };
29735
29879
  }
29736
- function AppointmentServicesNode({ sectionHeading, services, pathname, servicePathPrefix, className }) {
29880
+ function AppointmentServiceRows({ rows, RowTitle }) {
29881
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29882
+ className: "appointment-services-rows rb-flex rb-flex-col",
29883
+ children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("article", {
29884
+ className: "appointment-services-row rb-border-b rb-border-border rb-py-4",
29885
+ children: [
29886
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
29887
+ className: "appointment-services-row-main rb-min-w-0",
29888
+ children: [
29889
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RowTitle, {
29890
+ className: "appointment-services-row-title rb-text-lg rb-font-medium rb-leading-tight",
29891
+ children: row.detailsHref ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
29892
+ href: row.detailsHref,
29893
+ children: row.title
29894
+ }) : row.title
29895
+ }),
29896
+ row.description ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
29897
+ className: "appointment-services-row-description rb-text-sm rb-leading-6 status-muted",
29898
+ children: row.description
29899
+ }) : null,
29900
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
29901
+ className: "appointment-services-row-meta rb-caption status-muted",
29902
+ children: [row.durationLabel, row.locationLabel ? ` · ${row.locationLabel}` : ""]
29903
+ })
29904
+ ]
29905
+ }),
29906
+ row.priceLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
29907
+ className: "appointment-services-row-price rb-text-lg",
29908
+ children: row.priceLabel
29909
+ }) : null,
29910
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemedButtonLink, {
29911
+ variant: "secondary",
29912
+ size: "sm",
29913
+ extraClassName: "appointment-services-row-action",
29914
+ href: row.href,
29915
+ label: "Check times",
29916
+ screenReaderSuffix: ` for ${row.title}`
29917
+ })
29918
+ ]
29919
+ }, row.id))
29920
+ });
29921
+ }
29922
+ function AppointmentServiceGroups({ groups, hasSectionHeading, idPrefix }) {
29923
+ const GroupHeading = hasSectionHeading ? "h3" : "h2";
29924
+ const RowTitle = hasSectionHeading ? "h4" : "h3";
29925
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29926
+ className: "appointment-services-groups rb-flex rb-flex-col rb-gap-10",
29927
+ children: groups.map((group) => {
29928
+ const headingId = `${idPrefix}-group-${group.key}`;
29929
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
29930
+ className: "appointment-services-group rb-flex rb-flex-col rb-gap-2",
29931
+ "aria-labelledby": headingId,
29932
+ "data-appointment-services-group": group.key,
29933
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
29934
+ className: "appointment-services-group-heading",
29935
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(GroupHeading, {
29936
+ id: headingId,
29937
+ className: "appointment-services-group-title rb-text-sm rb-font-semibold rb-uppercase rb-tracking-wide",
29938
+ children: group.heading
29939
+ }), group.intro ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
29940
+ className: "appointment-services-group-intro rb-text-sm rb-leading-6 rb-max-w-prose status-muted",
29941
+ children: group.intro
29942
+ }) : null]
29943
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppointmentServiceRows, {
29944
+ rows: group.rows,
29945
+ RowTitle
29946
+ })]
29947
+ }, group.key);
29948
+ })
29949
+ });
29950
+ }
29951
+ function AppointmentServicesNode({ sectionHeading, categoryId, services, pathname, servicePathPrefix, blockId, className }) {
29737
29952
  const view = buildAppointmentServicesView({
29738
29953
  services,
29954
+ categoryId,
29739
29955
  pathname: typeof pathname === "string" ? pathname : undefined,
29740
29956
  servicePathPrefix: typeof servicePathPrefix === "string" ? servicePathPrefix : null
29741
29957
  });
29742
29958
  const rootClassName = `appointment-services ${className ?? ""}`.trim();
29743
- const RowTitle = sectionHeading ? "h3" : "h2";
29744
- if (view.kind === "error" || view.kind === "empty") {
29745
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29959
+ const hasSectionHeading = Boolean(sectionHeading);
29960
+ switch (view.kind) {
29961
+ case "error":
29962
+ case "empty": return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29746
29963
  className: rootClassName,
29747
29964
  "data-appointment-services-state": view.kind,
29748
29965
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
@@ -29751,51 +29968,25 @@ function AppointmentServicesNode({ sectionHeading, services, pathname, servicePa
29751
29968
  children: view.message
29752
29969
  })
29753
29970
  });
29971
+ case "items": return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29972
+ className: `${rootClassName} rb-flex rb-flex-col`,
29973
+ "data-appointment-services-state": "populated",
29974
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppointmentServiceRows, {
29975
+ rows: view.rows,
29976
+ RowTitle: hasSectionHeading ? "h3" : "h2"
29977
+ })
29978
+ });
29979
+ case "groups": return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29980
+ className: `${rootClassName} rb-flex rb-flex-col`,
29981
+ "data-appointment-services-state": "populated",
29982
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppointmentServiceGroups, {
29983
+ groups: view.groups,
29984
+ hasSectionHeading,
29985
+ idPrefix: `appointment-services-${blockId ?? "block"}`
29986
+ })
29987
+ });
29988
+ default: return assertNever(view);
29754
29989
  }
29755
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29756
- className: `${rootClassName} rb-flex rb-flex-col`,
29757
- "data-appointment-services-state": "populated",
29758
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
29759
- className: "appointment-services-rows rb-flex rb-flex-col",
29760
- children: view.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("article", {
29761
- className: "appointment-services-row rb-border-b rb-border-border rb-py-4",
29762
- children: [
29763
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
29764
- className: "appointment-services-row-main rb-min-w-0",
29765
- children: [
29766
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RowTitle, {
29767
- className: "appointment-services-row-title rb-text-lg rb-font-medium rb-leading-tight",
29768
- children: row.detailsHref ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
29769
- href: row.detailsHref,
29770
- children: row.title
29771
- }) : row.title
29772
- }),
29773
- row.description ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
29774
- className: "appointment-services-row-description rb-text-sm rb-leading-6 status-muted",
29775
- children: row.description
29776
- }) : null,
29777
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
29778
- className: "appointment-services-row-meta rb-caption status-muted",
29779
- children: [row.durationLabel, row.locationLabel ? ` · ${row.locationLabel}` : ""]
29780
- })
29781
- ]
29782
- }),
29783
- row.priceLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
29784
- className: "appointment-services-row-price rb-text-lg",
29785
- children: row.priceLabel
29786
- }) : null,
29787
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemedButtonLink, {
29788
- variant: "secondary",
29789
- size: "sm",
29790
- extraClassName: "appointment-services-row-action",
29791
- href: row.href,
29792
- label: "Check times",
29793
- screenReaderSuffix: ` for ${row.title}`
29794
- })
29795
- ]
29796
- }, row.id))
29797
- })
29798
- });
29799
29990
  }
29800
29991
  var appointment_services_default = AppointmentServicesNode;
29801
29992
 
@@ -31360,8 +31551,8 @@ function createLoadableValueState(tag, key, value) {
31360
31551
  };
31361
31552
  }
31362
31553
  function upsertRedirectRule(existing, next) {
31363
- if (existing.findIndex((rule$1) => rule$1.id === next.id) < 0) return [next, ...existing];
31364
- return existing.map((rule$1) => rule$1.id === next.id ? next : rule$1);
31554
+ if (existing.findIndex((rule$2) => rule$2.id === next.id) < 0) return [next, ...existing];
31555
+ return existing.map((rule$2) => rule$2.id === next.id ? next : rule$2);
31365
31556
  }
31366
31557
  function createInitialRedirectsClientState(initialState) {
31367
31558
  return {
@@ -31716,7 +31907,7 @@ function getWizardLocalStorage() {
31716
31907
  }
31717
31908
  }
31718
31909
  function parseEnvelope(raw, config$1) {
31719
- if (!isRecord$28(raw)) return {
31910
+ if (!isRecord$29(raw)) return {
31720
31911
  kind: "removed_invalid",
31721
31912
  reason: "invalid_envelope"
31722
31913
  };
@@ -31805,7 +31996,7 @@ function createWizardDraftStorage(config$1) {
31805
31996
  }
31806
31997
  };
31807
31998
  }
31808
- function isRecord$28(value) {
31999
+ function isRecord$29(value) {
31809
32000
  return typeof value === "object" && value !== null && !Array.isArray(value);
31810
32001
  }
31811
32002
  function deepEqualJsonValue(left, right) {
@@ -31815,8 +32006,8 @@ function deepEqualJsonValue(left, right) {
31815
32006
  if (left.length !== right.length) return false;
31816
32007
  return left.every((item, index) => deepEqualJsonValue(item, right[index]));
31817
32008
  }
31818
- if (isRecord$28(left) || isRecord$28(right)) {
31819
- if (!isRecord$28(left) || !isRecord$28(right)) return false;
32009
+ if (isRecord$29(left) || isRecord$29(right)) {
32010
+ if (!isRecord$29(left) || !isRecord$29(right)) return false;
31820
32011
  const leftKeys = Object.keys(left).sort();
31821
32012
  if (!deepEqualJsonValue(leftKeys, Object.keys(right).sort())) return false;
31822
32013
  return leftKeys.every((key) => deepEqualJsonValue(left[key], right[key]));
@@ -32278,12 +32469,12 @@ function resolveBookingSiteId(siteId, form$1) {
32278
32469
  return siteId || form$1?.siteId || "";
32279
32470
  }
32280
32471
  function normalizeBookingFormConfig(form$1) {
32281
- if (!isRecord$27(form$1)) return null;
32472
+ if (!isRecord$28(form$1)) return null;
32282
32473
  const id = readNonEmptyString(form$1.id);
32283
32474
  const name = readNonEmptyString(form$1.name);
32284
32475
  if (!id || !name) return null;
32285
- const schemaSource = isRecord$27(form$1.schema) ? form$1.schema : isRecord$27(form$1.schemaJson) ? form$1.schemaJson : null;
32286
- const settingsSource = isRecord$27(form$1.settings) ? form$1.settings : isRecord$27(form$1.settingsJson) ? form$1.settingsJson : null;
32476
+ const schemaSource = isRecord$28(form$1.schema) ? form$1.schema : isRecord$28(form$1.schemaJson) ? form$1.schemaJson : null;
32477
+ const settingsSource = isRecord$28(form$1.settings) ? form$1.settings : isRecord$28(form$1.settingsJson) ? form$1.settingsJson : null;
32287
32478
  return {
32288
32479
  id,
32289
32480
  name,
@@ -32307,10 +32498,10 @@ function normalizeBookingServices(services) {
32307
32498
  if (Array.isArray(services)) {
32308
32499
  return services.filter(isBookingFormService).map(normalizeService);
32309
32500
  }
32310
- if (isRecord$27(services) && "services" in services) {
32501
+ if (isRecord$28(services) && "services" in services) {
32311
32502
  return Array.isArray(services.services) ? services.services.filter(isBookingFormService).map(normalizeService) : [];
32312
32503
  }
32313
- return isRecord$27(services) ? Object.values(services).filter(isBookingFormService).map(normalizeService) : [];
32504
+ return isRecord$28(services) ? Object.values(services).filter(isBookingFormService).map(normalizeService) : [];
32314
32505
  }
32315
32506
  function normalizeBookingSchema(value) {
32316
32507
  if (!value) return undefined;
@@ -32322,7 +32513,7 @@ function normalizeBookingSchema(value) {
32322
32513
  };
32323
32514
  }
32324
32515
  function readBookingFormField(value) {
32325
- if (!isRecord$27(value)) return null;
32516
+ if (!isRecord$28(value)) return null;
32326
32517
  const id = readNonEmptyString(value.id);
32327
32518
  const label = readNonEmptyString(value.label);
32328
32519
  const type = readBookingFieldType(value.type);
@@ -32365,7 +32556,7 @@ function readBookingFieldOptions(value) {
32365
32556
  return options.length > 0 ? options : undefined;
32366
32557
  }
32367
32558
  function readBookingFieldOption(value) {
32368
- if (!isRecord$27(value)) return null;
32559
+ if (!isRecord$28(value)) return null;
32369
32560
  const optionValue = readNonEmptyString(value.value);
32370
32561
  const label = readNonEmptyString(value.label);
32371
32562
  return optionValue && label ? {
@@ -32385,7 +32576,7 @@ function normalizeBookingSettings(value) {
32385
32576
  };
32386
32577
  }
32387
32578
  function isBookingFormService(value) {
32388
- return isRecord$27(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === undefined || blockBookingConsentSchema.safeParse(value.consent).success);
32579
+ return isRecord$28(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === undefined || blockBookingConsentSchema.safeParse(value.consent).success);
32389
32580
  }
32390
32581
  function collectBookingCustomFields(data) {
32391
32582
  const customFields = {};
@@ -32441,7 +32632,7 @@ function readStringArray(value) {
32441
32632
  const strings = value.filter((item) => typeof item === "string");
32442
32633
  return strings.length > 0 ? strings : undefined;
32443
32634
  }
32444
- function isRecord$27(value) {
32635
+ function isRecord$28(value) {
32445
32636
  return typeof value === "object" && value !== null;
32446
32637
  }
32447
32638
  function getBookingServicePaymentSelectionMode(service) {
@@ -32670,6 +32861,41 @@ function getBookingSuccessMessage(successMessage) {
32670
32861
  return successMessage?.trim() || "Your appointment has been booked! Check your email for confirmation.";
32671
32862
  }
32672
32863
 
32864
+ //#endregion
32865
+ //#region ../blocks/src/system/runtime/nodes/booking-form.service-categories.ts
32866
+ function isRecord$27(value) {
32867
+ return typeof value === "object" && value !== null && !Array.isArray(value);
32868
+ }
32869
+ function readCategory(value) {
32870
+ if (!isRecord$27(value)) return [];
32871
+ const { id, name, description } = value;
32872
+ if (typeof id !== "string" || id.trim() === "" || typeof name !== "string") {
32873
+ return [];
32874
+ }
32875
+ return [{
32876
+ id,
32877
+ name,
32878
+ description: typeof description === "string" ? description : null
32879
+ }];
32880
+ }
32881
+ /**
32882
+ * The ordered service categories carried by a catalogue-shaped services
32883
+ * source (`{ services, categories }`). Every other shape has none, which
32884
+ * reads as a site without categories. Order is preserved: the database owns
32885
+ * it.
32886
+ */
32887
+ function normalizeBookingServiceCategories(services) {
32888
+ if (!isRecord$27(services) || !Array.isArray(services.categories)) return [];
32889
+ return services.categories.flatMap(readCategory);
32890
+ }
32891
+ /**
32892
+ * The ordered service categories a headless booking override was given in
32893
+ * its loader data (`data.services`).
32894
+ */
32895
+ function readOverrideServiceCategories(data) {
32896
+ return isRecord$27(data) ? normalizeBookingServiceCategories(data.services) : [];
32897
+ }
32898
+
32673
32899
  //#endregion
32674
32900
  //#region ../blocks/src/system/runtime/nodes/booking-runtime-sources.ts
32675
32901
  const APPOINTMENT_BOOKING_RUNTIME_FORM_ID = "booking-form";
@@ -32796,7 +33022,8 @@ function normalizeAppointmentBookingServices(input) {
32796
33022
  priceCurrency: service.priceCurrency,
32797
33023
  paymentTerms: service.paymentTerms,
32798
33024
  paymentTermsAvailability: service.paymentTermsAvailability,
32799
- consent: service.consent
33025
+ consent: service.consent,
33026
+ categoryId: service.categoryId ?? null
32800
33027
  }));
32801
33028
  }
32802
33029
  function buildAppointmentBookingSourceProps(input) {
@@ -32814,6 +33041,7 @@ function buildAppointmentBookingSourceProps(input) {
32814
33041
  siteId: input.siteId,
32815
33042
  services: input.services
32816
33043
  }),
33044
+ serviceCategories: [...input.serviceCategories ?? []],
32817
33045
  serviceId: input.serviceId,
32818
33046
  resourceId: input.resourceId,
32819
33047
  initialData: input.initialData,
@@ -32880,11 +33108,20 @@ const bookingServiceSchema = z.object({
32880
33108
  priceCurrency: z.string().nullable().optional(),
32881
33109
  paymentTerms: blockBookingPaymentTermsSchema.optional(),
32882
33110
  paymentTermsAvailability: blockBookingPaymentTermsAvailabilitySchema.optional(),
32883
- consent: blockBookingConsentSchema.optional()
33111
+ consent: blockBookingConsentSchema.optional(),
33112
+ categoryId: z.string().nullable().optional()
33113
+ });
33114
+ const bookingServiceCategorySchema = z.object({
33115
+ id: z.string(),
33116
+ name: z.string(),
33117
+ description: z.string().nullable()
32884
33118
  });
32885
33119
  const bookingServicesSourceSchema = z.union([
32886
33120
  z.array(bookingServiceSchema),
32887
- z.object({ services: z.array(bookingServiceSchema).optional() }),
33121
+ z.object({
33122
+ services: z.array(bookingServiceSchema).optional(),
33123
+ categories: z.array(bookingServiceCategorySchema).optional()
33124
+ }),
32888
33125
  z.record(z.string(), bookingServiceSchema)
32889
33126
  ]);
32890
33127
  const bookingConfigSchema = z.object({
@@ -32937,6 +33174,7 @@ const bookingHydrationSchema = z.object({
32937
33174
  rebookContext: z.object({ kind: z.literal("portal_rebook") }).nullable().optional(),
32938
33175
  form: bookingConfigSchema.nullable(),
32939
33176
  services: z.array(bookingServiceSchema),
33177
+ serviceCategories: z.array(bookingServiceCategorySchema).optional(),
32940
33178
  spamProtectionEnabled: z.boolean().optional()
32941
33179
  });
32942
33180
  const bookingRenderSchema = z.object({
@@ -32982,6 +33220,7 @@ const bookingFormBlockRuntime = defineClientOwnedBlockRuntime({
32982
33220
  rebookContext: source.rebookContext ?? null,
32983
33221
  form: form$1,
32984
33222
  services,
33223
+ serviceCategories: normalizeBookingServiceCategories(source.services),
32985
33224
  spamProtectionEnabled: source.spamProtectionEnabled
32986
33225
  }
32987
33226
  };
@@ -48792,6 +49031,125 @@ function generateFooterBottomTextLinkStyles(footer, themeId, tokens) {
48792
49031
  return cssChunks.join("\n");
48793
49032
  }
48794
49033
 
49034
+ //#endregion
49035
+ //#region ../theme-core/src/layout/boundaryCss.ts
49036
+ /**
49037
+ * Section-break CSS, generated from the transition catalogue (issue 1422).
49038
+ *
49039
+ * The renderer emits one element per shown join:
49040
+ *
49041
+ * <div data-block-boundary data-boundary-treatment="wave-edge-gentle"
49042
+ * data-boundary-placement="previous"
49043
+ * style="--boundary-shape-paint: …; --boundary-band-paint: …">
49044
+ *
49045
+ * Every selector matches an exact attribute value, so there is no reliance on
49046
+ * prefix ordering in the cascade and nothing reads colours out of a string.
49047
+ * The shape layer is `::after`; the band layer (paint → paint only) is
49048
+ * `::before`.
49049
+ */
49050
+ const waveMaskSvgPaths = {
49051
+ classic: "M0 72V46C130 55 260 24 420 30C590 36 690 62 860 42C1035 21 1160 47 1305 36C1365 31 1415 33 1440 38V72H0Z",
49052
+ balanced: "M0 72V42C120 24 240 24 360 42C480 60 600 60 720 42C840 24 960 24 1080 42C1200 60 1320 60 1440 42V72H0Z",
49053
+ gentle: "M0 72V43C180 32 300 32 480 43C660 54 780 54 960 43C1140 32 1260 32 1440 43V72H0Z",
49054
+ bold: "M0 72V44C90 14 270 14 360 44C450 74 630 74 720 44C810 14 990 14 1080 44C1170 74 1350 74 1440 44V72H0Z",
49055
+ drift: "M0 72V39C115 31 205 54 340 48C505 41 610 16 760 33C920 51 1015 64 1160 44C1280 27 1375 35 1440 50V72H0Z"
49056
+ };
49057
+ function waveMaskDataUrl(mask) {
49058
+ return svgDataUrl(`<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='${waveMaskSvgPaths[mask]}' fill='white'/></svg>`);
49059
+ }
49060
+ /**
49061
+ * How each placement anchors the layers against the zero-height boundary
49062
+ * element that sits exactly on the join.
49063
+ *
49064
+ * - `centred` straddles the join, band under shape.
49065
+ * - `previous` sits above the join, inside the previous section.
49066
+ * - `next` sits below the join, inside the next section, with the shape
49067
+ * flipped so it reads as the mirror of `previous`.
49068
+ */
49069
+ const placementRules = {
49070
+ centred: {
49071
+ shape: ["top: 0;", "transform: translateY(-50%);"],
49072
+ band: ["top: 0;", "transform: translateY(-50%);"]
49073
+ },
49074
+ previous: {
49075
+ shape: ["bottom: 0;"],
49076
+ band: null
49077
+ },
49078
+ next: {
49079
+ shape: ["top: 0;", "transform: scaleY(-1);"],
49080
+ band: null
49081
+ }
49082
+ };
49083
+ function generateBoundaryCss(selector) {
49084
+ const boundary = `${selector} [data-block-boundary]`;
49085
+ return [
49086
+ rule$1(boundary, [
49087
+ "position: relative;",
49088
+ "z-index: 3;",
49089
+ "height: 0;",
49090
+ "pointer-events: none;"
49091
+ ]),
49092
+ rule$1(`${selector} [data-block][data-site-style-boundary-before] > :where([data-slot='section'])`, ["padding-top: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-before-reserve, 0rem));"]),
49093
+ rule$1(`${selector} [data-block][data-site-style-boundary-after] > :where([data-slot='section'])`, ["padding-bottom: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-after-reserve, 0rem));"]),
49094
+ rule$1(`${boundary}::before,\n${boundary}::after`, [
49095
+ "content: '';",
49096
+ "position: absolute;",
49097
+ "left: 0;",
49098
+ "right: 0;",
49099
+ "height: var(--boundary-height);",
49100
+ "pointer-events: none;"
49101
+ ]),
49102
+ rule$1(`${boundary}::before`, ["z-index: 0;", "background: var(--boundary-band-paint, transparent);"]),
49103
+ rule$1(`${boundary}::after`, ["z-index: 1;"]),
49104
+ ...placementCss(boundary),
49105
+ ...transitionCatalogueEntries.flatMap((entry) => treatmentCss(boundary, entry))
49106
+ ].join("\n\n");
49107
+ }
49108
+ function placementCss(boundary) {
49109
+ return Object.keys(placementRules).flatMap((placement) => {
49110
+ const placed = `${boundary}[data-boundary-placement='${placement}']`;
49111
+ const rules = placementRules[placement];
49112
+ return [rule$1(`${placed}::after`, rules.shape), rules.band ? rule$1(`${placed}::before`, rules.band) : rule$1(`${placed}::before`, ["content: none;"])];
49113
+ });
49114
+ }
49115
+ function treatmentCss(boundary, entry) {
49116
+ const selected = `${boundary}[data-boundary-treatment='${entry.intent}']`;
49117
+ const metrics = boundaryRuntimeMetricsForDecorativeTransition(entry.intent);
49118
+ return [rule$1(selected, [`--boundary-height: ${metrics.heightCss};`]), rule$1(`${selected}::after`, shapeDeclarations(entry))];
49119
+ }
49120
+ function shapeDeclarations(entry) {
49121
+ switch (entry.shape.kind) {
49122
+ case "fade": return [
49123
+ "/* Fallback for browsers without color-mix(). */",
49124
+ "background: linear-gradient(to bottom, transparent 0%, var(--boundary-shape-paint) 100%);",
49125
+ "background: linear-gradient(to bottom, transparent 0%, color-mix(in srgb, var(--boundary-shape-paint) 16%, transparent) 34%, color-mix(in srgb, var(--boundary-shape-paint) 68%, transparent) 78%, var(--boundary-shape-paint) 100%);"
49126
+ ];
49127
+ case "wave": {
49128
+ const mask = waveMaskDataUrl(entry.shape.mask);
49129
+ return [
49130
+ "background: var(--boundary-shape-paint);",
49131
+ `mask-image: url("${mask}");`,
49132
+ "mask-repeat: no-repeat;",
49133
+ "mask-size: 100% 100%;",
49134
+ `-webkit-mask-image: url("${mask}");`,
49135
+ "-webkit-mask-repeat: no-repeat;",
49136
+ "-webkit-mask-size: 100% 100%;"
49137
+ ];
49138
+ }
49139
+ case "angle": return [
49140
+ "height: calc(var(--boundary-height) + 2px);",
49141
+ "background: var(--boundary-shape-paint);",
49142
+ `clip-path: ${anglePolygonCss(entry.shape.polygon)};`
49143
+ ];
49144
+ }
49145
+ }
49146
+ function rule$1(selector, declarations) {
49147
+ return `${selector} {\n ${declarations.join("\n ")}\n}`;
49148
+ }
49149
+ function svgDataUrl(svg) {
49150
+ return `data:image/svg+xml,${encodeURIComponent(svg)}`;
49151
+ }
49152
+
48795
49153
  //#endregion
48796
49154
  //#region ../theme-core/src/layout/generateLayoutCss.ts
48797
49155
  /**
@@ -48827,7 +49185,7 @@ function generateLayoutCss(options) {
48827
49185
  if (bodyTextTypographyCss) {
48828
49186
  cssChunks.push(bodyTextTypographyCss);
48829
49187
  }
48830
- cssChunks.push(generateSiteStyleTransitionCss(themeId));
49188
+ cssChunks.push(generateBoundaryCss(selector));
48831
49189
  return cssChunks.join("\n\n");
48832
49190
  }
48833
49191
  function toPaddingValue(preset) {
@@ -48839,197 +49197,6 @@ function toPaddingValue(preset) {
48839
49197
  };
48840
49198
  return map[preset];
48841
49199
  }
48842
- const classicWaveTransitionMask = svgDataUrl("<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='M0 72V46C130 55 260 24 420 30C590 36 690 62 860 42C1035 21 1160 47 1305 36C1365 31 1415 33 1440 38V72H0Z' fill='white'/></svg>");
48843
- const balancedWaveTransitionMask = svgDataUrl("<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='M0 72V42C120 24 240 24 360 42C480 60 600 60 720 42C840 24 960 24 1080 42C1200 60 1320 60 1440 42V72H0Z' fill='white'/></svg>");
48844
- const gentleWaveTransitionMask = svgDataUrl("<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='M0 72V43C180 32 300 32 480 43C660 54 780 54 960 43C1140 32 1260 32 1440 43V72H0Z' fill='white'/></svg>");
48845
- const boldWaveTransitionMask = svgDataUrl("<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='M0 72V44C90 14 270 14 360 44C450 74 630 74 720 44C810 14 990 14 1080 44C1170 74 1350 74 1440 44V72H0Z' fill='white'/></svg>");
48846
- const driftWaveTransitionMask = svgDataUrl("<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='M0 72V39C115 31 205 54 340 48C505 41 610 16 760 33C920 51 1015 64 1160 44C1280 27 1375 35 1440 50V72H0Z' fill='white'/></svg>");
48847
- function generateSiteStyleTransitionCss(themeId) {
48848
- const selector = `:where([data-theme-scope="${themeId}"])`;
48849
- const compactBoundaryHeight = requiredBoundaryHeightCss("wave-edge-gentle");
48850
- const mediumBoundaryHeight = requiredBoundaryHeightCss("wave-edge");
48851
- const largeBoundaryHeight = requiredBoundaryHeightCss("wave-edge-bold");
48852
- const spaciousBoundaryHeight = requiredBoundaryHeightCss("soft-fade-long");
48853
- const expansiveBoundaryHeight = requiredBoundaryHeightCss("soft-fade-expansive");
48854
- return `${selector} [data-style-group^='site-style-transition-'] {
48855
- position: relative;
48856
- z-index: 2;
48857
- --site-style-transition-from: transparent;
48858
- --site-style-transition-to: rgb(var(--tb-background));
48859
- }
48860
-
48861
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-'] {
48862
- position: relative;
48863
- z-index: 3;
48864
- height: 0;
48865
- pointer-events: none;
48866
- --site-style-boundary-height: ${mediumBoundaryHeight};
48867
- }
48868
-
48869
- ${selector} [data-block][data-site-style-boundary-before] > :where([data-slot='section']) {
48870
- padding-top: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-before-reserve, 0rem));
48871
- }
48872
-
48873
- ${selector} [data-block][data-site-style-boundary-after] > :where([data-slot='section']) {
48874
- padding-bottom: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-after-reserve, 0rem));
48875
- }
48876
-
48877
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::before,
48878
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::before,
48879
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::before {
48880
- content: '';
48881
- position: absolute;
48882
- left: 0;
48883
- right: 0;
48884
- top: 0;
48885
- transform: translateY(-50%);
48886
- z-index: 0;
48887
- height: var(--site-style-boundary-height);
48888
- background: var(--site-style-transition-from);
48889
- pointer-events: none;
48890
- }
48891
-
48892
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::after,
48893
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::after,
48894
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::after {
48895
- content: '';
48896
- position: absolute;
48897
- left: 0;
48898
- right: 0;
48899
- top: 0;
48900
- transform: translateY(-50%);
48901
- z-index: 1;
48902
- height: var(--site-style-boundary-height);
48903
- pointer-events: none;
48904
- }
48905
-
48906
- ${selector} [data-style-group*='-from-base-'] {
48907
- --site-style-transition-from: rgb(var(--tb-background));
48908
- }
48909
-
48910
- ${selector} [data-style-group*='-from-muted-band-'] {
48911
- --site-style-transition-from: rgb(var(--tb-muted));
48912
- }
48913
-
48914
- ${selector} [data-style-group*='-from-accent-band-'] {
48915
- --site-style-transition-from: rgb(var(--tb-accent));
48916
- }
48917
-
48918
- ${selector} [data-style-group*='-from-brand-depth-band-'] {
48919
- --site-style-transition-from: rgb(var(--tb-primary-800));
48920
- }
48921
-
48922
- ${selector} [data-style-group$='-to-base'],
48923
- ${selector} [data-style-group$='-to-hero-image'] {
48924
- --site-style-transition-to: rgb(var(--tb-background));
48925
- }
48926
-
48927
- ${selector} [data-style-group$='-to-muted-band'] {
48928
- --site-style-transition-to: rgb(var(--tb-muted));
48929
- }
48930
-
48931
- ${selector} [data-style-group$='-to-accent-band'] {
48932
- --site-style-transition-to: rgb(var(--tb-accent));
48933
- }
48934
-
48935
- ${selector} [data-style-group$='-to-brand-depth-band'] {
48936
- --site-style-transition-to: rgb(var(--tb-primary-800));
48937
- }
48938
-
48939
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::after {
48940
- /* Fallback for browsers without color-mix(). */
48941
- background: linear-gradient(
48942
- to bottom,
48943
- transparent 0%,
48944
- var(--site-style-transition-to) 100%
48945
- );
48946
- background: linear-gradient(
48947
- to bottom,
48948
- transparent 0%,
48949
- color-mix(in srgb, var(--site-style-transition-to) 16%, transparent) 34%,
48950
- color-mix(in srgb, var(--site-style-transition-to) 68%, transparent) 78%,
48951
- var(--site-style-transition-to) 100%
48952
- );
48953
- }
48954
-
48955
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade-long-'] {
48956
- --site-style-boundary-height: ${spaciousBoundaryHeight};
48957
- }
48958
-
48959
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade-expansive-'] {
48960
- --site-style-boundary-height: ${expansiveBoundaryHeight};
48961
- }
48962
-
48963
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::after {
48964
- height: calc(var(--site-style-boundary-height) + 2px);
48965
- background: var(--site-style-transition-to);
48966
- clip-path: polygon(0 42%, 100% 8%, 100% 100%, 0 100%);
48967
- }
48968
-
48969
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-subtle-'] {
48970
- --site-style-boundary-height: ${compactBoundaryHeight};
48971
- }
48972
-
48973
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-subtle-']::after {
48974
- clip-path: polygon(0 34%, 100% 18%, 100% 100%, 0 100%);
48975
- }
48976
-
48977
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-steep-'] {
48978
- --site-style-boundary-height: ${largeBoundaryHeight};
48979
- }
48980
-
48981
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-steep-']::after {
48982
- clip-path: polygon(0 54%, 100% 0%, 100% 100%, 0 100%);
48983
- }
48984
-
48985
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::after {
48986
- background: var(--site-style-transition-to);
48987
- mask-image: url("${classicWaveTransitionMask}");
48988
- mask-repeat: no-repeat;
48989
- mask-size: 100% 100%;
48990
- -webkit-mask-image: url("${classicWaveTransitionMask}");
48991
- -webkit-mask-repeat: no-repeat;
48992
- -webkit-mask-size: 100% 100%;
48993
- }
48994
-
48995
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-balanced-']::after {
48996
- mask-image: url("${balancedWaveTransitionMask}");
48997
- -webkit-mask-image: url("${balancedWaveTransitionMask}");
48998
- }
48999
-
49000
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-gentle-'] {
49001
- --site-style-boundary-height: ${compactBoundaryHeight};
49002
- }
49003
-
49004
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-gentle-']::after {
49005
- mask-image: url("${gentleWaveTransitionMask}");
49006
- -webkit-mask-image: url("${gentleWaveTransitionMask}");
49007
- }
49008
-
49009
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-bold-'] {
49010
- --site-style-boundary-height: ${largeBoundaryHeight};
49011
- }
49012
-
49013
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-bold-']::after {
49014
- mask-image: url("${boldWaveTransitionMask}");
49015
- -webkit-mask-image: url("${boldWaveTransitionMask}");
49016
- }
49017
-
49018
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-drift-']::after {
49019
- mask-image: url("${driftWaveTransitionMask}");
49020
- -webkit-mask-image: url("${driftWaveTransitionMask}");
49021
- }`;
49022
- }
49023
- function requiredBoundaryHeightCss(transition) {
49024
- const metrics = boundaryRuntimeMetricsForTransition(transition);
49025
- if (!metrics) {
49026
- throw new Error(`Missing boundary runtime metrics for ${transition}`);
49027
- }
49028
- return metrics.heightCss;
49029
- }
49030
- function svgDataUrl(svg) {
49031
- return `data:image/svg+xml,${encodeURIComponent(svg)}`;
49032
- }
49033
49200
  function generateHeroTypographyCss(themeId, theme) {
49034
49201
  const typography = theme.hero?.typography;
49035
49202
  if (!typography) return "";
@@ -49056,36 +49223,29 @@ function generateHeroTypographyCss(themeId, theme) {
49056
49223
  if (microSubheadlineRules.length > 0) {
49057
49224
  chunks.push(`${microSubheadlineSelector} {\n ${microSubheadlineRules.join("\n ")}\n}`);
49058
49225
  }
49059
- if (typography.responsive?.tablet) {
49060
- const tabletHeadlineRules = buildTypographyRules("headline", typography.responsive.tablet.headlineSize, typography.responsive.tablet.headlineLineHeight);
49061
- const tabletSubheadlineRules = buildTypographyRules("subheadline", typography.responsive.tablet.subheadlineSize, typography.responsive.tablet.subheadlineLineHeight);
49062
- const tabletChunks = [];
49063
- if (tabletHeadlineRules.length > 0) {
49064
- tabletChunks.push(`${headlineSelector} {\n ${tabletHeadlineRules.join("\n ")}\n }`);
49065
- }
49066
- if (tabletSubheadlineRules.length > 0) {
49067
- tabletChunks.push(`${subheadlineSelector} {\n ${tabletSubheadlineRules.join("\n ")}\n }`);
49068
- }
49069
- if (tabletChunks.length > 0) {
49070
- chunks.push(`@media ${MQ_TABLET_ONLY} {\n ${tabletChunks.join("\n\n ")}\n}`);
49071
- }
49072
- }
49073
- if (typography.responsive?.mobile) {
49074
- const mobileHeadlineRules = buildTypographyRules("headline", typography.responsive.mobile.headlineSize, typography.responsive.mobile.headlineLineHeight);
49075
- const mobileSubheadlineRules = buildTypographyRules("subheadline", typography.responsive.mobile.subheadlineSize, typography.responsive.mobile.subheadlineLineHeight);
49076
- const mobileChunks = [];
49077
- if (mobileHeadlineRules.length > 0) {
49078
- mobileChunks.push(`${headlineSelector} {\n ${mobileHeadlineRules.join("\n ")}\n }`);
49079
- }
49080
- if (mobileSubheadlineRules.length > 0) {
49081
- mobileChunks.push(`${subheadlineSelector} {\n ${mobileSubheadlineRules.join("\n ")}\n }`);
49082
- }
49083
- if (mobileChunks.length > 0) {
49084
- chunks.push(`@media ${MQ_SM_DOWN} {\n ${mobileChunks.join("\n\n ")}\n}`);
49085
- }
49226
+ const selectors = {
49227
+ headline: headlineSelector,
49228
+ subheadline: subheadlineSelector
49229
+ };
49230
+ const responsiveChunks = [responsiveHeroTypographyCss(MQ_TABLET_ONLY, selectors, typography.responsive?.tablet), responsiveHeroTypographyCss(MQ_SM_DOWN, selectors, typography.responsive?.mobile)];
49231
+ for (const chunk of responsiveChunks) {
49232
+ if (chunk) chunks.push(chunk);
49086
49233
  }
49087
49234
  return chunks.join("\n\n");
49088
49235
  }
49236
+ function responsiveHeroTypographyCss(media$1, selectors, overrides) {
49237
+ if (!overrides) return null;
49238
+ const headlineRules = buildTypographyRules("headline", overrides.headlineSize, overrides.headlineLineHeight);
49239
+ const subheadlineRules = buildTypographyRules("subheadline", overrides.subheadlineSize, overrides.subheadlineLineHeight);
49240
+ const mediaChunks = [];
49241
+ if (headlineRules.length > 0) {
49242
+ mediaChunks.push(`${selectors.headline} {\n ${headlineRules.join("\n ")}\n }`);
49243
+ }
49244
+ if (subheadlineRules.length > 0) {
49245
+ mediaChunks.push(`${selectors.subheadline} {\n ${subheadlineRules.join("\n ")}\n }`);
49246
+ }
49247
+ return mediaChunks.length > 0 ? `@media ${media$1} {\n ${mediaChunks.join("\n\n ")}\n}` : null;
49248
+ }
49089
49249
  function buildTypographyRules(role, size, lineHeight) {
49090
49250
  const rules = [];
49091
49251
  if (size) {
@@ -50302,7 +50462,7 @@ function escapeStyleContent(css) {
50302
50462
  function compileCustomCssRules(rules, themeId) {
50303
50463
  if (!rules || rules.length === 0) return "";
50304
50464
  const scopePrefix = `:where([data-theme-scope="${themeId}"])`;
50305
- return rules.map((rule$1) => compileRule(rule$1.selector, rule$1.declarations, scopePrefix)).filter(Boolean).join("\n\n");
50465
+ return rules.map((rule$2) => compileRule(rule$2.selector, rule$2.declarations, scopePrefix)).filter(Boolean).join("\n\n");
50306
50466
  }
50307
50467
  /**
50308
50468
  * Compile an array of block-specific CSS rules to a scoped CSS string.
@@ -50315,15 +50475,15 @@ function compileCustomCssRules(rules, themeId) {
50315
50475
  function compileBlockCustomCssRules(rules, themeId) {
50316
50476
  if (!rules || rules.length === 0) return "";
50317
50477
  const rulesByBlock = new Map();
50318
- for (const rule$1 of rules) {
50319
- const existing = rulesByBlock.get(rule$1.blockKind) || [];
50320
- existing.push(rule$1);
50321
- rulesByBlock.set(rule$1.blockKind, existing);
50478
+ for (const rule$2 of rules) {
50479
+ const existing = rulesByBlock.get(rule$2.blockKind) || [];
50480
+ existing.push(rule$2);
50481
+ rulesByBlock.set(rule$2.blockKind, existing);
50322
50482
  }
50323
50483
  const cssChunks = [];
50324
50484
  for (const [blockKind, blockRules] of rulesByBlock) {
50325
50485
  const scopePrefix = `:where([data-theme-scope="${themeId}"]) :where([data-block="${blockKind}"])`;
50326
- const blockCss = blockRules.map((rule$1) => compileRule(rule$1.selector, rule$1.declarations, scopePrefix)).filter(Boolean).join("\n");
50486
+ const blockCss = blockRules.map((rule$2) => compileRule(rule$2.selector, rule$2.declarations, scopePrefix)).filter(Boolean).join("\n");
50327
50487
  if (blockCss) {
50328
50488
  cssChunks.push(`/* Block: ${blockKind} */\n${blockCss}`);
50329
50489
  }
@@ -50575,7 +50735,7 @@ function compileFontFace(atRule) {
50575
50735
  * }
50576
50736
  */
50577
50737
  function compileMedia(atRule, scopePrefix, options) {
50578
- const rules = atRule.rules.map((rule$1) => compileNestedRule(rule$1.selector, rule$1.declarations, scopePrefix)).filter(Boolean).join("\n");
50738
+ const rules = atRule.rules.map((rule$2) => compileNestedRule(rule$2.selector, rule$2.declarations, scopePrefix)).filter(Boolean).join("\n");
50579
50739
  if (!rules) return "";
50580
50740
  const containerQuery = options.responsiveMode === "container" ? convertMediaQueryToContainerQuery(atRule.query) : null;
50581
50741
  if (containerQuery) {
@@ -50593,7 +50753,7 @@ function compileMedia(atRule, scopePrefix, options) {
50593
50753
  * }
50594
50754
  */
50595
50755
  function compileSupports(atRule, scopePrefix) {
50596
- const rules = atRule.rules.map((rule$1) => compileNestedRule(rule$1.selector, rule$1.declarations, scopePrefix)).filter(Boolean).join("\n");
50756
+ const rules = atRule.rules.map((rule$2) => compileNestedRule(rule$2.selector, rule$2.declarations, scopePrefix)).filter(Boolean).join("\n");
50597
50757
  if (!rules) return "";
50598
50758
  return `@supports ${atRule.condition} {\n${indent(rules, 2)}\n}`;
50599
50759
  }
@@ -50679,20 +50839,20 @@ function diffCssRules(before, after) {
50679
50839
  });
50680
50840
  }
50681
50841
  }
50682
- for (const [id, rule$1] of afterMap) {
50842
+ for (const [id, rule$2] of afterMap) {
50683
50843
  const beforeRule = beforeMap.get(id);
50684
50844
  if (!beforeRule) {
50685
50845
  patches.push({
50686
50846
  op: "add",
50687
- rule: rule$1
50847
+ rule: rule$2
50688
50848
  });
50689
- } else if (!rulesEqual(beforeRule, rule$1)) {
50849
+ } else if (!rulesEqual(beforeRule, rule$2)) {
50690
50850
  const changes = {};
50691
- if (beforeRule.selector !== rule$1.selector) {
50692
- changes.selector = rule$1.selector;
50851
+ if (beforeRule.selector !== rule$2.selector) {
50852
+ changes.selector = rule$2.selector;
50693
50853
  }
50694
- if (!declarationsEqual(beforeRule.declarations, rule$1.declarations)) {
50695
- changes.declarations = rule$1.declarations;
50854
+ if (!declarationsEqual(beforeRule.declarations, rule$2.declarations)) {
50855
+ changes.declarations = rule$2.declarations;
50696
50856
  }
50697
50857
  if (Object.keys(changes).length > 0) {
50698
50858
  patches.push({
@@ -51325,71 +51485,173 @@ function requireThemeResources(resources, blockKind) {
51325
51485
  }
51326
51486
 
51327
51487
  //#endregion
51328
- //#region ../blocks/src/pageRendererBoundaries.tsx
51329
- const boundaryKey = (previousBlockId, nextBlockId) => `${previousBlockId}\u0000${nextBlockId}`;
51330
- function isSiteStyleTransitionStyleGroup(value) {
51331
- return value?.startsWith("site-style-transition-") === true;
51488
+ //#region ../blocks/src/pageRenderJoins.ts
51489
+ /**
51490
+ * Re-points joins across omitted blocks. Pure over joins.
51491
+ *
51492
+ * Joins are keyed by adjacent block-id pairs, so dropping B from [A, B, C]
51493
+ * removes the A→C join as well as B's own, because no A→C join was ever
51494
+ * resolved. For each surviving pair the original join is kept when the pair
51495
+ * was already adjacent. Across a gap, the treatment comes from the join
51496
+ * entering the omitted run (A→first omitted), falling back to the one leaving
51497
+ * it (last omitted→C). Nothing visual is carried: the survivors are composed
51498
+ * from their own content afterwards.
51499
+ *
51500
+ * `survivors` are compared by identity against `originalBlocks`.
51501
+ */
51502
+ function retargetJoinsAcrossOmissions(input) {
51503
+ const { joins, originalBlocks, survivingBlocks } = input;
51504
+ if (joins.length === 0) return joins;
51505
+ if (originalBlocks.every((block) => survivingBlocks.has(block))) return joins;
51506
+ const byPair = joinsByPair(joins);
51507
+ const retargeted = [];
51508
+ let previousSurvivor = null;
51509
+ let run = [];
51510
+ for (const block of originalBlocks) {
51511
+ if (!survivingBlocks.has(block)) {
51512
+ run.push(block);
51513
+ continue;
51514
+ }
51515
+ if (previousSurvivor?.id && block.id) {
51516
+ const join$1 = run.length === 0 ? byPair.get(pairKey(previousSurvivor.id, block.id)) : joinAcrossRun(byPair, previousSurvivor.id, block.id, run);
51517
+ if (join$1) retargeted.push(join$1);
51518
+ }
51519
+ previousSurvivor = block;
51520
+ run = [];
51521
+ }
51522
+ return retargeted;
51523
+ }
51524
+ function joinAcrossRun(byPair, previousBlockId, nextBlockId, run) {
51525
+ const firstOmittedId = run[0]?.id;
51526
+ const lastOmittedId = run[run.length - 1]?.id;
51527
+ const entering = firstOmittedId ? byPair.get(pairKey(previousBlockId, firstOmittedId)) : undefined;
51528
+ const exiting = lastOmittedId ? byPair.get(pairKey(lastOmittedId, nextBlockId)) : undefined;
51529
+ const donor = entering ?? exiting;
51530
+ return donor ? {
51531
+ previousBlockId,
51532
+ nextBlockId,
51533
+ treatment: donor.treatment
51534
+ } : undefined;
51332
51535
  }
51333
- function boundaryMapForAdjacentBlockIds(boundaries) {
51334
- return new Map(boundaries.map((boundary) => [boundaryKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
51536
+ /**
51537
+ * The single composition site. For every adjacent pair of survivors with ids,
51538
+ * finds its join and composes it from both survivors' final content: what
51539
+ * each paints with and the frame each draws.
51540
+ */
51541
+ function composePageJoins(input) {
51542
+ const byPair = joinsByPair(input.joins);
51543
+ return input.survivors.flatMap((previous, index) => {
51544
+ const next = input.survivors[index + 1];
51545
+ if (!next || !previous.id || !next.id) return [];
51546
+ const join$1 = byPair.get(pairKey(previous.id, next.id));
51547
+ return [{
51548
+ previousBlockId: previous.id,
51549
+ nextBlockId: next.id,
51550
+ join: join$1 ? composeJoin(join$1, previous, next) : null
51551
+ }];
51552
+ });
51335
51553
  }
51336
- function boundaryBetweenBlocks(previousBlock, nextBlock, boundaries) {
51337
- const previousBlockId = previousBlock?.id ?? null;
51338
- const nextBlockId = nextBlock?.id ?? null;
51339
- if (!previousBlockId || !nextBlockId) return null;
51340
- return boundaries.get(boundaryKey(previousBlockId, nextBlockId)) ?? null;
51554
+ function composeJoin(join$1, previous, next) {
51555
+ return {
51556
+ boundaryId: pairKey(join$1.previousBlockId, join$1.nextBlockId),
51557
+ previousBlockId: join$1.previousBlockId,
51558
+ nextBlockId: join$1.nextBlockId,
51559
+ treatment: join$1.treatment,
51560
+ outcome: composeBoundary(sectionFactsForContent(previous.content), sectionFactsForContent(next.content))
51561
+ };
51341
51562
  }
51342
- function renderBoundaryAfterBlock(input) {
51343
- const previousBlockId = input.block.id;
51344
- const nextBlockId = input.nextBlock?.id ?? null;
51563
+ /** Placement, reserves and paint for a join that draws; null otherwise. */
51564
+ function renderedJoinLayout(join$1) {
51565
+ if (!join$1 || join$1.outcome.kind !== "shown") return null;
51566
+ return boundaryLayout(join$1.treatment, join$1.outcome.composition);
51567
+ }
51568
+ /** Joins with a missing id on either side cannot sit between two blocks. */
51569
+ function joinsByPair(joins) {
51570
+ return new Map(joins.flatMap((join$1) => join$1.previousBlockId && join$1.nextBlockId ? [[pairKey(join$1.previousBlockId, join$1.nextBlockId), join$1]] : []));
51571
+ }
51572
+ /** Slot lookup key for an adjacent pair, in the one boundary-id scheme. */
51573
+ function joinSlotKey(previousBlockId, nextBlockId) {
51574
+ return pairKey(previousBlockId, nextBlockId);
51575
+ }
51576
+ function pairKey(previousBlockId, nextBlockId) {
51577
+ return makeBoundaryId(asDesignBlockId(previousBlockId), asDesignBlockId(nextBlockId));
51578
+ }
51579
+
51580
+ //#endregion
51581
+ //#region ../blocks/src/pageRendererBoundaries.tsx
51582
+ function joinSlotsByPair(slots) {
51583
+ return new Map(slots.map((slot) => [joinSlotKey(slot.previousBlockId, slot.nextBlockId), slot]));
51584
+ }
51585
+ function joinSlotBetween(previousBlockId, nextBlockId, slots) {
51345
51586
  if (!previousBlockId || !nextBlockId) return null;
51346
- const boundary = input.boundaryByAdjacentBlockIds.get(boundaryKey(previousBlockId, nextBlockId));
51347
- if (!boundary) return null;
51348
- const rendered = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
51587
+ return slots.get(joinSlotKey(previousBlockId, nextBlockId)) ?? null;
51588
+ }
51589
+ /**
51590
+ * The markup at one join: the painted boundary element when the join draws,
51591
+ * then whatever `renderJoin` adds. Null when there is nothing to render.
51592
+ */
51593
+ function renderJoinSlot(input) {
51594
+ const { slot } = input;
51595
+ if (!slot) return null;
51596
+ const layout = renderedJoinLayout(slot.join);
51597
+ const painted = slot.join && layout ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
51349
51598
  "aria-hidden": "true",
51350
51599
  "data-block-boundary": "",
51351
- "data-boundary-id": boundary.boundaryId,
51352
- "data-boundary-previous-block-id": boundary.previousBlockId,
51353
- "data-boundary-next-block-id": boundary.nextBlockId,
51354
- "data-style-group": boundary.styleGroup,
51355
- style: boundaryCssVarsStyle(boundary)
51356
- }, `${input.blockKey}:boundary`);
51357
- return input.wrapBoundary ? input.wrapBoundary(boundary, rendered) : rendered;
51600
+ "data-boundary-id": slot.join.boundaryId,
51601
+ "data-boundary-previous-block-id": slot.previousBlockId,
51602
+ "data-boundary-next-block-id": slot.nextBlockId,
51603
+ "data-boundary-treatment": slot.join.treatment,
51604
+ "data-boundary-placement": layout.placement,
51605
+ style: boundaryPaintStyle(layout.paint)
51606
+ }, `${input.blockKey}:boundary`) : null;
51607
+ const extra = input.renderJoin?.({
51608
+ previousBlockId: slot.previousBlockId,
51609
+ nextBlockId: slot.nextBlockId,
51610
+ join: slot.join
51611
+ });
51612
+ if (!painted && (extra === null || extra === undefined)) return null;
51613
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.Fragment, { children: [painted, extra] }, `${input.blockKey}:join`);
51358
51614
  }
51359
- function boundaryCssVarsStyle(boundary) {
51360
- return {
51361
- "--site-style-transition-from": boundary.cssVars.from,
51362
- "--site-style-transition-to": boundary.cssVars.to
51363
- };
51615
+ /**
51616
+ * The one render-edge place a theme token becomes CSS.
51617
+ */
51618
+ function themeTokenCss(token) {
51619
+ return `rgb(var(--tb-${token}))`;
51364
51620
  }
51365
- function boundaryReserveCssVarsStyle(input) {
51366
- const before = input.before?.metrics.reserveCss ?? null;
51367
- const after = input.after?.metrics.reserveCss ?? null;
51368
- if (!before && !after) return undefined;
51621
+ function boundaryPaintStyle(paint) {
51369
51622
  return {
51370
- ...before ? { "--site-style-boundary-before-reserve": before } : {},
51371
- ...after ? { "--site-style-boundary-after-reserve": after } : {}
51623
+ "--boundary-shape-paint": themeTokenCss(paint.shape),
51624
+ ...paint.band ? { "--boundary-band-paint": themeTokenCss(paint.band) } : {}
51372
51625
  };
51373
51626
  }
51374
- function transitionCssVarsStyle(content) {
51375
- const transitionStyles = isObjectRecord(content._transitionStyles) ? content._transitionStyles : null;
51376
- const cssVars = isObjectRecord(transitionStyles?.cssVars) ? transitionStyles.cssVars : null;
51377
- const from = typeof cssVars?.from === "string" ? cssVars.from : null;
51378
- const to = typeof cssVars?.to === "string" ? cssVars.to : null;
51379
- if (!from && !to) return undefined;
51380
- return {
51381
- ...from ? { "--site-style-transition-from": from } : {},
51382
- ...to ? { "--site-style-transition-to": to } : {}
51627
+ /**
51628
+ * Reserve attributes and variables for a block wrapper, from the joins on
51629
+ * either side. A join's `before` reserve sits inside its previous section, so
51630
+ * it pads the bottom of the block above the join; its `after` reserve pads the
51631
+ * top of the block below. Joins that do not draw reserve nothing.
51632
+ */
51633
+ function blockBoundaryReserves(input) {
51634
+ const top = reserveOnSide(input.before, "after");
51635
+ const bottom = reserveOnSide(input.after, "before");
51636
+ const style = {
51637
+ ...top ? { "--site-style-boundary-before-reserve": top.length } : {},
51638
+ ...bottom ? { "--site-style-boundary-after-reserve": bottom.length } : {}
51383
51639
  };
51384
- }
51385
- function mergeStyleObjects(left, right) {
51386
- if (!left) return right;
51387
- if (!right) return left;
51388
51640
  return {
51389
- ...left,
51390
- ...right
51641
+ topPlacement: top?.placement,
51642
+ bottomPlacement: bottom?.placement,
51643
+ style: top || bottom ? style : undefined
51391
51644
  };
51392
51645
  }
51646
+ /** The reserve one join needs on one side of itself, if it draws at all. */
51647
+ function reserveOnSide(slot, side) {
51648
+ const layout = renderedJoinLayout(slot?.join ?? null);
51649
+ const length = layout?.reserves[side] ?? null;
51650
+ return layout && length ? {
51651
+ length,
51652
+ placement: layout.placement
51653
+ } : null;
51654
+ }
51393
51655
  function pageBlockPosition(index, blockCount) {
51394
51656
  if (blockCount <= 1) return "only";
51395
51657
  if (index === 0) return "first";
@@ -56735,6 +56997,15 @@ const appointmentServicesManifest = createBlockManifest({
56735
56997
  required: false,
56736
56998
  ui: { intent: "content" }
56737
56999
  },
57000
+ createSiteOptionSelectField({
57001
+ id: "categoryId",
57002
+ label: "Category",
57003
+ description: "Show one category (with its heading and intro), or leave it on All categories to show everything grouped.",
57004
+ required: false,
57005
+ multiple: false,
57006
+ optionSource: "appointmentServiceCategories",
57007
+ ui: { intent: "content" }
57008
+ }),
56738
57009
  sectionStylesField({
56739
57010
  id: "_sectionStyles",
56740
57011
  label: "Section styles",
@@ -56769,6 +57040,7 @@ const appointmentServicesManifest = createBlockManifest({
56769
57040
  richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description")),
56770
57041
  el("appointment-services", {
56771
57042
  sectionHeading: { $bind: { from: "content.heading" } },
57043
+ categoryId: { $bind: { from: "content.categoryId" } },
56772
57044
  services: { $bind: { from: "data.services" } },
56773
57045
  pathname: { $bind: { from: "$root.pathname" } },
56774
57046
  servicePathPrefix: { $bind: { from: "$root.appointmentServicePathPrefix" } }
@@ -62382,11 +62654,10 @@ function buildPageRenderPlan(input) {
62382
62654
  return {
62383
62655
  entries,
62384
62656
  omitted,
62385
- runtimeBoundaries: retargetBoundariesAcrossOmissions({
62386
- boundaries: input.runtimeBoundaries,
62657
+ runtimeJoins: retargetJoinsAcrossOmissions({
62658
+ joins: input.runtimeJoins ?? [],
62387
62659
  originalBlocks: input.page.blocks,
62388
- survivingBlocks: new Set(entries.map((entry) => entry.block)),
62389
- previewStage: input.previewStage
62660
+ survivingBlocks: new Set(entries.map((entry) => entry.block))
62390
62661
  })
62391
62662
  };
62392
62663
  }
@@ -62495,88 +62766,15 @@ function getBlockOverride(blockKind, overrides) {
62495
62766
  * For draft/preview: draftContent takes priority over content.
62496
62767
  * For published: content is the source of truth.
62497
62768
  */
62769
+ /**
62770
+ * The content a block renders with. Every render path (public, preview,
62771
+ * templates, SDK) chooses content here, so this is also where a stale
62772
+ * `site-style-transition-*` styleGroup is dropped; authored values survive.
62773
+ */
62498
62774
  function getRenderableContent(block, previewStage = "published") {
62499
62775
  const source = previewStage === "preview" && block.draftContent ? block.draftContent : block.content ?? {};
62500
- return isObjectRecord(source) ? source : {};
62501
- }
62502
- /**
62503
- * Re-points section boundaries across omitted blocks.
62504
- *
62505
- * Boundaries are keyed by adjacent block-id *pairs*, so dropping B from
62506
- * [A, B, C] does not merely remove B's boundaries — it removes the boundary
62507
- * between the survivors, because no `A→C` key was ever compiled.
62508
- *
62509
- * Per decision D2 the treatment for a merged gap comes from the boundary
62510
- * entering the omitted run (`A→firstOmitted`), falling back to the one leaving
62511
- * it (`lastOmitted→C`). The destination surface is a separate question: only
62512
- * the exiting boundary knows what C looks like, so the merged boundary ends on
62513
- * that endpoint rather than on the omitted block's. In the exiting-only
62514
- * fallback the origin remains an approximation: `from` is the omitted block's
62515
- * surface, because no compiled boundary carries A's. The hosted path no longer
62516
- * needs this at all (site-renderer admits blocks before compiling boundaries);
62517
- * it remains the safety net for an SDK whose compiled boundaries predate an
62518
- * omission.
62519
- */
62520
- function retargetBoundariesAcrossOmissions(input) {
62521
- const { boundaries, originalBlocks, survivingBlocks, previewStage } = input;
62522
- if (boundaries === undefined) return undefined;
62523
- if (boundaries.length === 0) return boundaries;
62524
- const anyOmitted = originalBlocks.some((block) => !survivingBlocks.has(block));
62525
- if (!anyOmitted) return boundaries;
62526
- const byPair = new Map(boundaries.map((boundary) => [pairKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
62527
- const retargeted = [];
62528
- let previousSurvivor = null;
62529
- let run = [];
62530
- for (const block of originalBlocks) {
62531
- if (!survivingBlocks.has(block)) {
62532
- run.push(block);
62533
- continue;
62534
- }
62535
- if (previousSurvivor?.id && block.id) {
62536
- const boundary = run.length === 0 ? byPair.get(pairKey(previousSurvivor.id, block.id)) : mergedBoundary({
62537
- byPair,
62538
- previousBlock: previousSurvivor,
62539
- previousBlockId: previousSurvivor.id,
62540
- nextBlock: block,
62541
- nextBlockId: block.id,
62542
- run,
62543
- previewStage
62544
- });
62545
- if (boundary) retargeted.push(boundary);
62546
- }
62547
- previousSurvivor = block;
62548
- run = [];
62549
- }
62550
- return retargeted;
62551
- }
62552
- function mergedBoundary(input) {
62553
- const { byPair, previousBlock, previousBlockId, nextBlock, nextBlockId, run, previewStage } = input;
62554
- const firstOmittedId = run[0]?.id;
62555
- const lastOmittedId = run[run.length - 1]?.id;
62556
- const entering = firstOmittedId ? byPair.get(pairKey(previousBlockId, firstOmittedId)) : undefined;
62557
- const exiting = lastOmittedId ? byPair.get(pairKey(lastOmittedId, nextBlockId)) : undefined;
62558
- const donor = entering ?? exiting;
62559
- if (!donor) return undefined;
62560
- const transition = compileTransitionRuntime({
62561
- treatment: donor.treatment,
62562
- from: donor.from,
62563
- to: exiting?.to ?? donor.to
62564
- });
62565
- const rekeyed = {
62566
- ...donor,
62567
- boundaryId: makeBoundaryId(asDesignBlockId(previousBlockId), asDesignBlockId(nextBlockId)),
62568
- previousBlockId,
62569
- nextBlockId,
62570
- from: transition.from,
62571
- to: transition.to,
62572
- cssVars: transition.cssVars
62573
- };
62574
- return retargetCompiledBoundaryRuntimeForContentAdjacency(rekeyed, {
62575
- currentContent: getRenderableContent(previousBlock, previewStage),
62576
- nextContent: getRenderableContent(nextBlock, previewStage)
62577
- });
62776
+ return isObjectRecord(source) ? stripStaleTransitionStyleGroup(source) : {};
62578
62777
  }
62579
- const pairKey = boundaryKey;
62580
62778
 
62581
62779
  //#endregion
62582
62780
  //#region ../blocks/src/system/manifest/hydrateLinks.ts
@@ -62774,25 +62972,47 @@ function preparePageRender(input) {
62774
62972
  prepared
62775
62973
  });
62776
62974
  }
62777
- if (constructionFailures.length === 0 || input.audience === "authoring") {
62778
- return {
62779
- entries,
62780
- constructionFailures,
62781
- runtimeBoundaries: input.plan.runtimeBoundaries
62782
- };
62783
- }
62784
- const survivors = entries.filter((candidate) => candidate.prepared.kind !== "render-threw");
62975
+ const survivors = input.audience === "authoring" ? entries : entries.filter((candidate) => candidate.prepared.kind !== "render-threw");
62785
62976
  return {
62786
62977
  entries: survivors,
62787
62978
  constructionFailures,
62788
- runtimeBoundaries: retargetBoundariesAcrossOmissions({
62789
- boundaries: input.plan.runtimeBoundaries,
62790
- originalBlocks: input.plan.entries.map((planned) => planned.block),
62791
- survivingBlocks: new Set(survivors.map((survivor) => survivor.entry.block)),
62792
- previewStage: input.previewStage
62793
- })
62979
+ joins: composeSurvivorJoins(input.plan, survivors, input.themeResources)
62794
62980
  };
62795
62981
  }
62982
+ /**
62983
+ * The only place joins are composed. Plan-time omissions were already
62984
+ * re-pointed by the plan; construction failures are re-pointed here by the
62985
+ * same primitive, so `[A, B, C]` losing B still gets an A→C join. Each
62986
+ * survivor is then classified from its final content.
62987
+ */
62988
+ function composeSurvivorJoins(plan, survivors, themeResources) {
62989
+ const joins = retargetJoinsAcrossOmissions({
62990
+ joins: plan.runtimeJoins,
62991
+ originalBlocks: plan.entries.map((planned) => planned.block),
62992
+ survivingBlocks: new Set(survivors.map((survivor) => survivor.entry.block))
62993
+ });
62994
+ return composePageJoins({
62995
+ joins,
62996
+ survivors: survivors.map(({ entry }) => ({
62997
+ id: entry.block.id,
62998
+ kind: entry.block.kind,
62999
+ content: boundaryClassificationContent(entry, themeResources)
63000
+ }))
63001
+ });
63002
+ }
63003
+ /**
63004
+ * The content a join is classified from must be the content that paints the
63005
+ * section. The default block renderer layers the theme's per-block style
63006
+ * defaults (`theme.blockOverrides[kind].styles`, background included) under
63007
+ * the block's own `_sectionStyles`, so classify that same projection. An
63008
+ * override component renders its own markup from the unmerged content.
63009
+ */
63010
+ function boundaryClassificationContent(entry, themeResources) {
63011
+ if (entry.kind !== "renderable" || entry.override) {
63012
+ return entry.renderableContent;
63013
+ }
63014
+ return mergeContentWithBlockStyleOverrides(entry.renderableContent, themeResources.kind === "available" ? themeResources.config : undefined, entry.block.kind);
63015
+ }
62796
63016
  function prepareEntry(entry, input) {
62797
63017
  if (entry.kind !== "renderable") return { kind: "plan-decided" };
62798
63018
  const draftContent = previewableEditorContent(entry);
@@ -63006,7 +63226,7 @@ function EditorPageRenderer(props$1) {
63006
63226
  });
63007
63227
  }
63008
63228
  function PageRendererCore(props$1) {
63009
- const { page, audience, runtimeBoundaries, dataContext, routeMap, registry, sdkConfig, customBlocks, blockOverrides } = props$1;
63229
+ const { page, audience, runtimeJoins, dataContext, routeMap, registry, sdkConfig, customBlocks, blockOverrides } = props$1;
63010
63230
  const imageResolveContext = import_react.useMemo(() => resolvePageRendererImageResolveContext(dataContext), [
63011
63231
  dataContext?.imageResolveContext,
63012
63232
  dataContext?.mediaAssets,
@@ -63022,7 +63242,7 @@ function PageRendererCore(props$1) {
63022
63242
  blocks: []
63023
63243
  },
63024
63244
  audience,
63025
- runtimeBoundaries,
63245
+ runtimeJoins,
63026
63246
  previewStage,
63027
63247
  sdkConfig,
63028
63248
  customBlocks,
@@ -63030,7 +63250,7 @@ function PageRendererCore(props$1) {
63030
63250
  }), [
63031
63251
  page,
63032
63252
  audience,
63033
- runtimeBoundaries,
63253
+ runtimeJoins,
63034
63254
  previewStage,
63035
63255
  sdkConfig,
63036
63256
  customBlocks,
@@ -63047,19 +63267,16 @@ function PageRendererCore(props$1) {
63047
63267
  dataContext,
63048
63268
  routeMap,
63049
63269
  themeResources,
63050
- imageResolveContext,
63051
- previewStage
63270
+ imageResolveContext
63052
63271
  }), [
63053
63272
  plan,
63054
63273
  audience,
63055
63274
  dataContext,
63056
63275
  routeMap,
63057
63276
  themeResources,
63058
- imageResolveContext,
63059
- previewStage
63277
+ imageResolveContext
63060
63278
  ]);
63061
- const boundaryByAdjacentBlockIds = import_react.useMemo(() => boundaryMapForAdjacentBlockIds(preparation.runtimeBoundaries ?? []), [preparation.runtimeBoundaries]);
63062
- const usesRuntimeBoundaries = preparation.runtimeBoundaries !== undefined;
63279
+ const joinSlots = import_react.useMemo(() => joinSlotsByPair(preparation.joins), [preparation.joins]);
63063
63280
  reportAndReplayOmissions({
63064
63281
  plan,
63065
63282
  preparation,
@@ -63079,10 +63296,9 @@ function PageRendererCore(props$1) {
63079
63296
  themeResources,
63080
63297
  imageResolveContext,
63081
63298
  resolvedRegistry,
63082
- boundaryByAdjacentBlockIds,
63083
- usesRuntimeBoundaries,
63299
+ joinSlots,
63084
63300
  wrapBlock: props$1.wrapBlock,
63085
- wrapBoundary: props$1.wrapBoundary
63301
+ renderJoin: props$1.renderJoin
63086
63302
  })) });
63087
63303
  }
63088
63304
  /**
@@ -63177,27 +63393,25 @@ function requireEntryBlockOverride(entry, themeResources) {
63177
63393
  }
63178
63394
  /**
63179
63395
  * Renders one planned block: its wrapper, style-group data attributes,
63180
- * boundary reserve space and any boundary or transition sibling that follows.
63396
+ * boundary reserve space and the join markup that follows it.
63181
63397
  *
63182
63398
  * Extracted from PageRendererCore so the component stays about wiring the plan
63183
63399
  * rather than also owning per-block markup.
63184
63400
  */
63185
63401
  function renderPlanEntry(input) {
63186
- const { index, entries, props: props$1, themeResources, imageResolveContext, resolvedRegistry, boundaryByAdjacentBlockIds, usesRuntimeBoundaries, wrapBlock, wrapBoundary } = input;
63402
+ const { index, entries, props: props$1, themeResources, imageResolveContext, resolvedRegistry, joinSlots, wrapBlock, renderJoin } = input;
63187
63403
  const entry = input.entry.entry;
63188
63404
  const block = entry.block;
63189
63405
  const blockPosition = pageBlockPosition(index, entries.length);
63190
63406
  const blockKey = blockKeyFor(block, index);
63191
63407
  const previousBlock = entries[index - 1]?.entry.block ?? null;
63192
63408
  const nextBlock = entries[index + 1]?.entry.block ?? null;
63193
- const boundaryBefore = boundaryBetweenBlocks(previousBlock, block, boundaryByAdjacentBlockIds);
63194
- const boundaryAfter = boundaryBetweenBlocks(block, nextBlock, boundaryByAdjacentBlockIds);
63195
- const boundaryElement = renderBoundaryAfterBlock({
63196
- block,
63197
- nextBlock,
63409
+ const joinBefore = joinSlotBetween(previousBlock?.id, block.id, joinSlots);
63410
+ const joinAfter = joinSlotBetween(block.id, nextBlock?.id, joinSlots);
63411
+ const boundaryElement = renderJoinSlot({
63412
+ slot: joinAfter,
63198
63413
  blockKey,
63199
- boundaryByAdjacentBlockIds,
63200
- wrapBoundary
63414
+ renderJoin
63201
63415
  });
63202
63416
  const cleanKind = block.kind.replace(/^block\./, "");
63203
63417
  const managesOwnWrapper = entry.kind === "renderable" && entry.managesOwnWrapper;
@@ -63220,12 +63434,10 @@ function renderPlanEntry(input) {
63220
63434
  const styleGroup = typeof renderableContent.styleGroup === "string" ? renderableContent.styleGroup : undefined;
63221
63435
  const blockLook = typeof renderableContent.blockLook === "string" ? renderableContent.blockLook : undefined;
63222
63436
  const blockComposition = readBlockComposition(renderableContent.blockComposition);
63223
- const transitionStyle = transitionCssVarsStyle(renderableContent);
63224
- const boundaryReserveStyle = boundaryReserveCssVarsStyle({
63225
- before: boundaryBefore,
63226
- after: boundaryAfter
63437
+ const reserves = blockBoundaryReserves({
63438
+ before: joinBefore,
63439
+ after: joinAfter
63227
63440
  });
63228
- const blockStyle = mergeStyleObjects(transitionStyle, boundaryReserveStyle);
63229
63441
  const blockElement = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
63230
63442
  className: wrapperClassName,
63231
63443
  "data-block": cleanKind,
@@ -63237,25 +63449,16 @@ function renderPlanEntry(input) {
63237
63449
  "data-block-composition": blockComposition?.treatment,
63238
63450
  "data-block-composition-kind": blockComposition?.kind,
63239
63451
  "data-page-block-position": blockPosition,
63240
- "data-site-style-boundary-before": boundaryBefore?.metrics.size,
63241
- "data-site-style-boundary-after": boundaryAfter?.metrics.size,
63452
+ "data-site-style-boundary-before": reserves.topPlacement,
63453
+ "data-site-style-boundary-after": reserves.bottomPlacement,
63242
63454
  "data-style-group": styleGroup,
63243
- style: blockStyle,
63455
+ style: reserves.style,
63244
63456
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MemoBlockSection, { ...blockSectionProps })
63245
63457
  }, blockKey);
63246
63458
  if (boundaryElement) {
63247
63459
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.Fragment, { children: [blockElement, boundaryElement] }, blockKey);
63248
63460
  }
63249
- if (usesRuntimeBoundaries || !isSiteStyleTransitionStyleGroup(styleGroup)) {
63250
- return blockElement;
63251
- }
63252
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.Fragment, { children: [blockElement, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
63253
- "aria-hidden": "true",
63254
- "data-block-transition": "",
63255
- "data-block-transition-for": block.id ?? blockKey,
63256
- "data-style-group": styleGroup,
63257
- style: transitionStyle
63258
- })] }, blockKey);
63461
+ return blockElement;
63259
63462
  }
63260
63463
  function resolvePageRendererImageResolveContext(dataContext) {
63261
63464
  const input = dataContext?.imageResolveContext;
@@ -81223,6 +81426,17 @@ function coerceToString(value) {
81223
81426
  return "";
81224
81427
  }
81225
81428
  /**
81429
+ * The appointment-services catalogue (issue 1403) pairs the services with the
81430
+ * site's ordered categories. Both are needed to group them, so that response
81431
+ * is passed through whole; any other `{ services }` wrapper is unwrapped.
81432
+ */
81433
+ function coerceServicesPayload(record) {
81434
+ return Array.isArray(record.services) && Array.isArray(record.categories) ? {
81435
+ services: record.services,
81436
+ categories: record.categories
81437
+ } : record.services;
81438
+ }
81439
+ /**
81226
81440
  * Extract data from API response.
81227
81441
  * Unwraps common response wrappers: { data }, { form }, { entries }, { services }, { teamMembers }
81228
81442
  *
@@ -81237,7 +81451,7 @@ function coercePayload(payload) {
81237
81451
  if ("data" in record && typeof record.data !== "function") return record.data;
81238
81452
  if ("form" in record && typeof record.form !== "function") return record.form;
81239
81453
  if ("entries" in record && typeof record.entries !== "function") return record.entries;
81240
- if ("services" in record && typeof record.services !== "function") return record.services;
81454
+ if ("services" in record && typeof record.services !== "function") return coerceServicesPayload(record);
81241
81455
  if ("teamMembers" in record && typeof record.teamMembers !== "function") return record.teamMembers;
81242
81456
  }
81243
81457
  return payload;
@@ -101805,7 +102019,7 @@ async function loadConfig(configPath, output) {
101805
102019
  return { errorMessage: "No config path provided." };
101806
102020
  }
101807
102021
  try {
101808
- const { loadConfigFile: loadConfigFile$1 } = await import("../load-config-CKBdp8M0.mjs");
102022
+ const { loadConfigFile: loadConfigFile$1 } = await import("../load-config-BToL9PfS.mjs");
101809
102023
  const rawConfig = await loadConfigFile$1(configPath);
101810
102024
  const parsed = riverbankSiteConfigSchema.safeParse(rawConfig);
101811
102025
  if (!parsed.success) {
@@ -113214,6 +113428,7 @@ const allowedPathPrefixes = [
113214
113428
  "/sites/{siteId}/bookings/closures",
113215
113429
  "/sites/{siteId}/bookings/resources",
113216
113430
  "/sites/{siteId}/bookings/services",
113431
+ "/sites/{siteId}/bookings/service-categories",
113217
113432
  "/sites/{siteId}/bookings/appointment-setup",
113218
113433
  "/sites/{siteId}/bookings/appointments",
113219
113434
  "/sites/{siteId}/bookings/appointment-reservations",
@@ -113347,13 +113562,13 @@ function classifyDemoDashboardMutationDescriptor(descriptor) {
113347
113562
  publicMessage: descriptor.demoMutation.publicMessage
113348
113563
  };
113349
113564
  }
113350
- for (const rule$1 of demoMutationClassificationRules) {
113351
- if (!matchesClassificationRule(descriptor, rule$1)) continue;
113565
+ for (const rule$2 of demoMutationClassificationRules) {
113566
+ if (!matchesClassificationRule(descriptor, rule$2)) continue;
113352
113567
  return {
113353
113568
  kind: "classified",
113354
113569
  capability,
113355
- classification: rule$1.classification,
113356
- publicMessage: rule$1.publicMessage
113570
+ classification: rule$2.classification,
113571
+ publicMessage: rule$2.publicMessage
113357
113572
  };
113358
113573
  }
113359
113574
  return {
@@ -113362,12 +113577,12 @@ function classifyDemoDashboardMutationDescriptor(descriptor) {
113362
113577
  publicMessage: "This demo action needs a safety classification before it can run."
113363
113578
  };
113364
113579
  }
113365
- function matchesClassificationRule(descriptor, rule$1) {
113366
- switch (rule$1.kind) {
113367
- case "path_starts_with": return rule$1.prefixes.some((prefix) => descriptor.path.startsWith(prefix));
113368
- case "path_fragments": return pathContainsAnySegmentSequence(descriptor.path, rule$1.fragments);
113369
- case "path_prefixes": return rule$1.prefixes.some((prefix) => descriptor.path.startsWith(prefix));
113370
- default: return assertNever(rule$1);
113580
+ function matchesClassificationRule(descriptor, rule$2) {
113581
+ switch (rule$2.kind) {
113582
+ case "path_starts_with": return rule$2.prefixes.some((prefix) => descriptor.path.startsWith(prefix));
113583
+ case "path_fragments": return pathContainsAnySegmentSequence(descriptor.path, rule$2.fragments);
113584
+ case "path_prefixes": return rule$2.prefixes.some((prefix) => descriptor.path.startsWith(prefix));
113585
+ default: return assertNever(rule$2);
113371
113586
  }
113372
113587
  }
113373
113588
  function isDashboardSiteMutation(descriptor) {
@@ -114125,25 +114340,10 @@ function deriveLaunchDomainRequirement(siteId, domainState) {
114125
114340
  } : evidenceFromDerivedState(state)
114126
114341
  });
114127
114342
  }
114128
- function deriveLaunchReadiness(input) {
114129
- const siteId = input.siteId ?? null;
114130
- const overrides = input.overrides ?? [];
114131
- const appointmentOverrideDefinition = LAUNCH_REQUIREMENT_OVERRIDE_DEFINITIONS.configure_appointments;
114343
+ function generatedSiteReviewRequirements(input) {
114344
+ if (input.publicExperience === "hosted_booking") return [];
114132
114345
  const isGeneratedSiteReviewed = input.reviewGeneratedSite.kind === "reviewed";
114133
- const requirements = [];
114134
- const confirmEmailState = input.email.kind === "confirmed" ? "complete" : "pending";
114135
- requirements.push(requirement({
114136
- id: "confirm_email",
114137
- packId: "core",
114138
- priority: "required",
114139
- state: confirmEmailState,
114140
- owner: "either",
114141
- title: "Confirm your email",
114142
- description: confirmEmailState === "complete" ? "Email confirmed. Your dashboard and launch path are unlocked." : "Confirm your email before the site can go live.",
114143
- action: informationalAction("Confirm your email"),
114144
- evidence: evidenceFromDerivedState(confirmEmailState)
114145
- }));
114146
- requirements.push(requirement({
114346
+ return [requirement({
114147
114347
  id: "review_generated_site",
114148
114348
  packId: "core",
114149
114349
  priority: "required",
@@ -114165,7 +114365,26 @@ function deriveLaunchReadiness(input) {
114165
114365
  kind: "deferred",
114166
114366
  reason: "pending_acknowledgement"
114167
114367
  }
114368
+ })];
114369
+ }
114370
+ function deriveLaunchReadiness(input) {
114371
+ const siteId = input.siteId ?? null;
114372
+ const overrides = input.overrides ?? [];
114373
+ const appointmentOverrideDefinition = LAUNCH_REQUIREMENT_OVERRIDE_DEFINITIONS.configure_appointments;
114374
+ const requirements = [];
114375
+ const confirmEmailState = input.email.kind === "confirmed" ? "complete" : "pending";
114376
+ requirements.push(requirement({
114377
+ id: "confirm_email",
114378
+ packId: "core",
114379
+ priority: "required",
114380
+ state: confirmEmailState,
114381
+ owner: "either",
114382
+ title: "Confirm your email",
114383
+ description: confirmEmailState === "complete" ? "Email confirmed. Your dashboard and launch path are unlocked." : "Confirm your email before the site can go live.",
114384
+ action: informationalAction("Confirm your email"),
114385
+ evidence: evidenceFromDerivedState(confirmEmailState)
114168
114386
  }));
114387
+ requirements.push(...generatedSiteReviewRequirements(input));
114169
114388
  if (input.appointments.kind !== "disabled") {
114170
114389
  const appointmentsOverride = input.appointments.kind === "pending_setup" ? findLaunchOverride(overrides, appointmentOverrideDefinition.requirementId) : null;
114171
114390
  requirements.push(requirement({
@@ -114624,7 +114843,8 @@ const appointmentSetupServiceDraftSchema = z.object({
114624
114843
  venue: appointmentSetupVenueChoiceSchema,
114625
114844
  timezone: z.string().trim().min(1).nullable(),
114626
114845
  payment: appointmentSetupPaymentDraftSchema,
114627
- assignment: appointmentSetupServiceAssignmentDraftSchema
114846
+ assignment: appointmentSetupServiceAssignmentDraftSchema,
114847
+ categoryId: z.string().uuid().nullable().optional()
114628
114848
  }).superRefine((service, ctx) => {
114629
114849
  if (service.payment.kind !== "free" && (service.priceAmount === null || service.priceAmount <= 0)) {
114630
114850
  ctx.addIssue({
@@ -118663,9 +118883,9 @@ function isNegationForEnvRule(line) {
118663
118883
  function normalizePattern(pattern) {
118664
118884
  return pattern.replace(/^\.\//u, "").replace(/\/$/u, "");
118665
118885
  }
118666
- function appendGitignoreRule(content, rule$1) {
118667
- if (content.length === 0) return `${rule$1}\n`;
118668
- return `${content.endsWith("\n") ? content : `${content}\n`}${rule$1}\n`;
118886
+ function appendGitignoreRule(content, rule$2) {
118887
+ if (content.length === 0) return `${rule$2}\n`;
118888
+ return `${content.endsWith("\n") ? content : `${content}\n`}${rule$2}\n`;
118669
118889
  }
118670
118890
  /**
118671
118891
  * Plan the one exact environment rule needed by local onboarding. Existing