riverbankcms 0.22.0 → 0.22.2

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 createNavigationRef, $n as cardRoleSchema, $r as __toESM, A as eventManagementTiming, An as runtimeMediaSchema, Ar as backgroundColorStyle, At as BREAKPOINT_SM, B as bookingPaymentCollectionOptionSchema, Bn as deriveVideoSlotPolicy, Br as PUBLIC_BUTTON_ROLES, Bt as themeButtonSizeClassName, C as stableStringify, Cn as getDirectImageUrlForRelativePath, Cr as tileTreatmentValues, Ct as themeSchema, Dn as mediaPosterSchema, Dr as surfaceDecorationColors, Dt as renderHeaderPaintTargetRules, E as mapEventCommandContent, En as isSiteScopedDirectImageStoragePath, Er as resolveSurface, Et as getHeaderPaintTargets, F as pageIdentifierForCommand, Fn as buildMediaIdentifierFromFilename, Fr as opaqueSurfaceDeclarations, Ft as getComponentShadowVar, G as createBlockRef, Gr as surfaceForegroundInputFromResolved, Gt as buildTextContrastShadow, H as require_jsx_runtime, Hr as surfaceToneFromForegroundToken, Ht as parseThemeForPersistence, I as pagePathForCommand, In as parseCanonicalMediaIdentifier, Ir as isCssColorLiteral, It as ICON_CONFIG, J as createContentEntryRef, Jr as expandPalette, Jt as arrangementSlotNames, K as createBlocksRef, Kn as parseRelativeStoragePath, Kr as surfaceForegroundStyle, Kt as generateNavLinkCSS, L as hasConfiguredBookingVertical, Ln as parseMediaIdentifier, Lr as resolveOnColorForThemeToken, Lt as generateDefaultButtonSystem, M as blockOrderIdentifiersForCommand, Mn as resolveManagedMediaReference, Mr as mergeStyles, Mt as MQ_MD_DOWN, N as entryIdentifierForCommand, Nn as mediaTypeDiscriminants, Nr as rgbColorValue, Nt as headerVariantValues, On as mediaSchema, Or as resolveSurfaceForeground, Ot as BREAKPOINT_LG, P as pageIdentifierForBlockCommand, Pn as mediaIdentifierSchema, Pr as textColorStyle, Pt as getComponentBorderWidthPx, Q as createFormRef, Qn as renderCssDeclarations, Qr as __commonJSMin, R as eventCommandScheduleSchema, Rr as resolveThemeColorReferenceValue, Rt as parseThemeButtonVariantId, S as sortRecord, Sn as resolveMediaTransformForAspect, Sr as testimonialsCompositionTreatmentValues, St as deriveSiteStyleBlockPolicy, Tn as getImageUrl, Tr as visibleHeadingDecorationFamilyValues, Tt as generateMobileOverlayBackdropFilterFix, U as require_react, Un as getMimeType, Ur as defaultThemeTokens, V as bookingPaymentTermsSchema, Vn as resolveVideoSelectionIntentForMediaField, Vr as selectLogoVariant, Vt as themeButtonVariantClassNames, W as ianaTimezoneSchema, Wn as asRecord, Wr as surfaceForegroundDeclarations, Wt as footerForegroundCandidates, X as createEventSeriesRef, Xn as managementMediaLibraryUploadLimitForMimeType, Xr as parseThemeColorToken, Xt as resolveLookSlotSurfaces, Y as createEventCategoryRef, Yn as classifyKnownMimeType, Yr as getTextColorForBackground, Yt as resolveItemSurface, Z as createFooterRef, Zn as parseSafeDownloadFilename, Zr as parseBlockColorToken, _n as looksLikeUuid, _r as featureCardCompositionTreatmentValues, _t as requireCuratedSiteStyle, an as parseCanonicalRuntimeBackground, ar as cardListKnobBlocks, at as effectiveHeaderNavLinkStyle, b as isEventVisibility, bn as isExplicitRectCrop, br as makeBoundaryId, bt as getGradientPreset, c as customLinkSchema$1, cn as boxBackgroundOverlaySchema, cr as asLayoutVariantKey, ct as transparentHeaderForegroundInput, d as internalLinkSchema, dn as siteChromeBackgroundOverrideOwner, dr as assertNever$11, dt as readHeaderNavFontSizePreference, en as headerRootPaint, er as cardVariantRoleValues, et as createPageRef, f as internalResolvedLinkSchema, fn as stripStaleTransitionStyleGroup, fr as defineBlockDesignCapability, ft as arrangementManifestFromVariants, g as linkSchema, gn as deleteEnvSpecificKeys, gr as eyebrowTreatmentValues, gt as resolvePublishedLook, h as isResolvedInternalLinkEntityType, hn as walkAndCollectImageAttrs, hr as eventListTreatmentValues, ht as resolveModeArrangement, in as deriveSectionGround, ir as cardListFamilyBlocks, j as blockIdentifierForCommand, jn as buildRenderableMediaAssetIndex, jr as borderColorStyle, jt as BREAKPOINT_XL, kn as persistableMediaSchema, kr as TokenResolver, kt as BREAKPOINT_MD, l as entryLinkSchema$1, ln as boxBackgroundSchema, lt as readOpeningForegroundProjection, mn as summarizePortableMediaReferences, mr as defineNoBlockDesignCapability, mt as resolveEffectiveArrangement, nn as composeBoundary, nr as defineBlockDesignMetadata, nt as createVenueRef, o as classifySdkEventBookingOffer, on as resolveSectionBackground, ot as headerForegroundCandidates, p as internalRouteOnlyLinkSchema, pn as collectPortableMediaReferencesFromContent, pr as defineFrameCapability, pt as readStoredMode, q as createCommandRef, qn as FILE_SIZE_LIMITS, qr as surfaceForegroundValue, qt as arrangementLookHooks, rn as sectionFactsForContent, rr as resolveEffectiveKnobs, rt as refSlug, s as RESOLVED_INTERNAL_LINK_ENTITY_TYPES, sn as resolveSectionPhotoPlacement, sr as asDesignBlockId, st as headerLeafTextColor, t as classifySdkEventTiming, tn as boundaryLayout, tr as normalizeCardRole, u as externalLinkSchema$1, un as runtimeBoxBackgroundSchema, ur as asSiteStyleId, ut as mergeHeaderNavTypographyPreference, v as pageLinkSchema$1, vn as rotateNormalizedPoint, vr as headingDecorationColorTokenValues, vt as compiledRuntimeStyleFieldNames, wn as getDirectImageUrlForSiteScopedPath, wr as toNonEmptyReadonlyArray, wt as generateHeaderScrollTreatmentStyles, x as venueManagementFields, xn as parseAspectRatioValue, xr as offeringsPreviewCompositionTreatmentValues, xt as parseGradientPresetId, yn as normalizeWidthCandidates, yr as introCompositionTreatmentValues, yt as gradientToCss, zr as resolveTokenOrCssColorValue, zt as themeButtonExtraClassesWithTrailingIcon } from "../sdk-event-upsert-BqjzK2zT.mjs";
3
- import { $ as compareLocalScheduleDates, $t as parseCustomerId, A as DEFAULT_MAILING_LIST_SUCCESS_MESSAGE, At as formatEventPricing, Bn as ok, Bt as mapReadonlyNonEmptyArray, Cn as unsafeAsOccurrenceId, Ct as omitZeroAmountFutureRows, D as parseMailingListSignedSourceToken, Dt as deriveTotalTicketQuantity, E as parseMailingListContentVersionId, En as unsafeAsPositiveByteCount, Et as resolveFlexibleBalanceInitialPaymentCents, F as DEFAULT_MAILING_LIST_TARGETING, Ft as publicEventPricingType, G as formatDateRange, Gt as toNonNegativeCents, H as toLocationCoordinates, Ht as parseNonNegativeCents, I as planMailingListChoicePresentation, It as resolveExactOrFreePriceDisplay, J as formatTime$1, Jt as formatCurrencyAmount, K as formatDateTime, Kt as unsafeAsNonNegativeCents, L as isCanonicalRichTextDoc, Lt as resolvePricingOptionsPriceDisplay, M as parseFooterCompositionV1, Mt as publicEventPricingCurrency, Nt as publicEventPricingDisplay, O as parseMailingListRuntimeContext, On as unsafeAsPricingOptionId, Ot as parseEventPageSurfaceSubject, P as parseMailingListPlacementTargeting, Pt as publicEventPricingOptions, Q as buildBookingUtcDateWindow, Qt as parseApiKeyId, Rt as fromArray, S as parseManagementKey, Sn as unsafeAsMediaAssetId, St as getBookingInstalmentProtectedDepositAmountCents, T as parseMailingListBlockId, Tn as unsafeAsPassId, Tt as resolveFlexibleBalanceDueAt, U as selectCategoryView, Ut as parsePositiveCents, V as normalizeLocationAddressText, Vt as isNonNegativeWholeCents$1, W as formatDate, Wt as toCents, X as inferSiteDateLocale, Xt as assertNever, Y as formatTimeRange, Yt as parseCurrencyCode, Z as resolveBookingDisplayContext, Zt as formatDurationUnitLabel, _ as orderedLiveRunSessions, _t as isSettleableOrderInstalmentPlanStatus, b as createSecret, bt as getBookingInstalmentDepositRefundability, c as resolveSiteVerticalBookingConfig, cn as parseSiteTermsVersionId, ct as resolveBookingBusinessTimeZone, dt as toScheduledBookingDateTime, en as parseDatedOfferingCapacityHoldAuditRef, et as formatLocalScheduleDate, f as SURFACE_EVENT_PRESET_VALUES, ft as toUtcIsoDateTimeString, g as nextLiveRunSessionStartMs, gt as isSettleableFlexibleBalanceContractStatus, h as CANCELLED_SESSION_STATUS, hn as unsafeAsDemoCapability, ht as isLiveOrderInstalmentPlanStatus, in as parseMembershipId, it as getLocalScheduleTime, j as parseMailingListProviderScope, jt as isPublicEventPricingOptionDisplayable, k as assessMailingListTargeting, kt as formatBookingPriceDisplay, ln as parseUserId, lt as resolveBookingTimeZoneId, mt as validateBookingPaymentOptionKinds, n as EVENT_PRESET_VOCABULARY, nn as parseEventSeriesId, nt as getBookingLocalDateKey, on as parseRetiredCourseId, ot as parseBookingLocalDate, p as WHOLE_SERIES_EVENT_PRESET_VALUES, pn as unsafeAsCheckoutLineId, q as formatDateTimeRange, qt as unsafeAsPositiveCents, r as NAVIGATION_LOCATIONS, rt as getLocalScheduleDate, sn as parseSiteId, st as parseUtcIsoDateTimeString, tn as parseDatedOfferingCapacityHoldId, tt as formatLocalScheduleTime, u as EVENT_PRESET_VALUES, ut as toBookingTimeZone, v as resolveWholeSeriesBookingPhase, vn as unsafeAsEventInstanceId, vt as addIntervalPreservingMonthAnchor, w as slugify, wt as splitAmountWithRemainderInFirst, x as exposeSecret, xt as getBookingInstalmentPlanIntervalCount, y as resolveWholeSeriesRunLifecycle, yt as clampConfiguredAmount, z as assertAllowedPublicUrl, zn as err } from "../src-zzwJUef5.mjs";
4
- import { $ as buildStorageUrl, $t as createClientForEnv, A as formatDiffDetail, At as convertPulledFooterToLocal, B as formatFieldPath, Bn as linkPayloadSchema, Bt as extractApiErrorMessage, C as reportFromOutcomes, Cn as planEnvironmentWriteAuthorization, Ct as readJsonFile, D as normalizeRemoteHomepageReference, Dn as formatError, Dt as duplicateNavigationIdentityMessage, E as createEmptySyncResult, En as ErrorCodes, Et as isMediaValue, F as calculatePullDiff, Fn as loadSdkEnvironment, Ft as applyPushConfigPlan, G as prepareLocalMediaForPush, Gt as createBearerAPIClient, Hn as sdkConfiguredBookingVerticalConfigSchema, Ht as isRecord$2, I as buildJsonDiff, In as createManagementClient, It as createPushConfigCommand, J as writeBinaryFileAtomic, Jt as deriveCmsApiUrlFromDashboardUrl, K as admitBinaryWriteDestination, Kt as ApiEnvelopeError, Ln as ManagementApiError, Lt as loadPushConfigTarget, M as hasPendingChanges, Mn as getEnvironmentDebugInfo, Mt as convertPulledPagesToLocal, N as findChangedFieldsWithValues, Nn as isLocalConfigured, Nt as navigationCommandItemToManagementInput, O as calculateDiff, On as transportSuggestion, Ot as navigationIdentity, P as formatHumanDiff, Pn as isRemoteConfigured, Pt as normalizeNavigationConfigItemForCommand, Q as walkLocalContentMedia, Qt as confirmAction, R as buildFieldChanges, Rn as sdkEventScheduleSchema, Rt as preparePushConfigTarget, S as planCliCommandBatchForBindings, Sn as planEnvironmentExecution, St as listFiles, T as executeResidualSettingsSyncPlan, Tn as withRiverbankEnv, Tt as writeJsonFileAtomic, U as syncLocalMediaForPull, Ut as APPOINTMENT_SERVICE_VISIBILITY_VALUES, V as revalidateLocalMediaPushPlan, Vn as portablePageDesignSchema, Vt as normalizeApiError, W as applyLocalMediaPush, Wt as ApiRequestError, X as normalizeMediaIdentifierInput, Xt as mapEnvironmentExecutionOutcomesJson, Y as sha256, Yt as API_ENDPOINTS, Z as normalizeMediaValueToIdentifierOnly, Zt as bindRequestTimeout, _ as commandExecutionToReport, _n as withConfirmation, _t as readMetadataFile, a as executeEntryCommandPlanAsReport, an as formatDateShort, at as readEventCategoriesMeta, b as executeCompiledCliCommandPlanAsReport, bn as executeEnvironmentPlan, bt as ensureDir, c as titleFromEntryData, cn as handleCommandError, ct as readFormsMeta, d as unsafeAsCliEntryIdentifier, dn as parseJsonArray, dt as readSettingsMeta, en as createCommandContext, et as downloadMedia, f as unsafeAsCliFormSlug, fn as parseJsonData, ft as readVenuesMeta, g as commandCompileFailureToReport, gn as withCommandContext, gt as metadataTargetFromEnvironment, h as mutationCounterForEffect, hn as runWithInvocation, ht as metadataFilePath, i as entrySiteCommandTypes, in as currentInvocationEnvelope, it as readAllMeta, j as formatDiffSummary, jn as getEnvPrefix, jt as convertPulledNavigationToLocal, k as calculateFooterDiff, kn as CliExit, kt as navigationIdentityMap, l as unsafeAsCliBlockIdentifier, ln as managementClientConfigFor, lt as readNavigationMeta, m as commandReportToSyncResult, mn as resolveCommandPath, mt as ensureMetadataDir, n as entryCommandCompileErrorToReport, nn as createListCommand, nt as findLegacyEntryFiles, o as executeEntryCommandPlanAsSyncResult, on as getOutputContext, ot as readEventsMeta, p as unsafeAsCliPageIdentifier, pn as parsePaginationOptions, pt as writeMediaManifest, q as readStableBinaryFile, qt as unwrapResponse, r as entryCommandReportToSyncResult, rn as createOutputForCommand, rt as readEntries, s as syncResultForEntryCommandCompileError, sn as getOutputOnlyContext, st as readFooterMeta, t as compileEntryPushCommands, tn as createGetCommand, tt as extractMediaIdentifiers, u as unsafeAsCliContentTypeKey, un as mapEntryForOutput, ut as readPagesMeta, v as commandReportHasFailures, vn as withErrorHandling, vt as writeMetadataFile, w as mergeManySyncResults, wn as requireEnvironmentWriteAuthorization, wt as writeJsonFile, x as formatManagementError, xn as parseEnvironmentSelection, xt as fileExists, y as executeCliCommandPlan, yn as withOutputOnlyErrorHandling, yt as writeMetadataRecordMap, z as formatFieldChange, zn as sdkSettingsUpdateSchema, zt as revalidatePushConfigPlan } from "../entryCommands-CoGu0G2R.mjs";
5
- import { r as CLI_VERSION, t as loadConfigFile } from "../load-config-j58KSqxl.mjs";
6
- import { a as RichTextDocSchema, c as isRecord$18, i as findUnfilledSentinels, n as buildEntryScaffold, o as isRichTextDoc, r as validateEntryInstance, s as isObjectRecord, t as eventUpsertInputSchema } from "../eventUpsertInputSchema-B0ubhUu5.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-C4IphkPc.mjs";
2
+ import { $ as createFormRef, $n as managementMediaLibraryUploadLimitForMimeType, $r as parseThemeColorToken, An as mediaPosterSchema, Ar as surfaceDecorationColors, At as BREAKPOINT_MD, Bn as parseMediaIdentifier, Br as resolveOnColorForThemeToken, Bt as themeButtonExtraClassesWithTrailingIcon, C as sortRecord, Cn as isExplicitRectCrop, Cr as makeBoundaryId, Ct as deriveSiteStyleBlockPolicy, D as mapEventCommandContent, Dn as getDirectImageUrlForSiteScopedPath, Dr as toNonEmptyReadonlyArray, Dt as getHeaderPaintTargets, En as getDirectImageUrlForRelativePath, Er as tileTreatmentValues, Et as generateMobileOverlayBackdropFilterFix, F as pageIdentifierForBlockCommand, Fn as resolveManagedMediaReference, Fr as mergeStyles, Ft as getComponentBorderWidthPx, G as ianaTimezoneSchema, Gr as surfaceToneFromForegroundToken, Gt as footerForegroundCandidates, H as bookingPaymentTermsSchema, Hr as resolveTokenOrCssColorValue, Ht as themeButtonVariantClassNames, I as pageIdentifierForCommand, In as mediaTypeDiscriminants, Ir as rgbColorValue, It as getComponentShadowVar, J as createCommandRef, Jr as surfaceForegroundInputFromResolved, Jt as arrangementLookHooks, K as createBlockRef, Kn as getMimeType, Kr as defaultThemeTokens, Kt as buildTextContrastShadow, L as pagePathForCommand, Ln as mediaIdentifierSchema, Lr as textColorStyle, Lt as ICON_CONFIG, M as blockIdentifierForCommand, Mn as persistableMediaSchema, Mr as TokenResolver, Mt as BREAKPOINT_XL, N as blockOrderIdentifiersForCommand, Nn as runtimeMediaSchema, Nr as backgroundColorStyle, Nt as MQ_MD_DOWN, On as getImageUrl, Or as visibleHeadingDecorationFamilyValues, Ot as renderHeaderPaintTargetRules, P as entryIdentifierForCommand, Pn as buildRenderableMediaAssetIndex, Pr as borderColorStyle, Pt as headerVariantValues, Q as createFooterRef, Qn as classifyKnownMimeType, Qr as getTextColorForBackground, R as hasConfiguredBookingVertical, Rn as buildMediaIdentifierFromFilename, Rr as opaqueSurfaceDeclarations, Rt as generateDefaultButtonSystem, S as venueManagementFields, Sn as normalizeWidthCandidates, Sr as introCompositionTreatmentValues, St as parseGradientPresetId, Tn as resolveMediaTransformForAspect, Tr as testimonialsCompositionTreatmentValues, Tt as generateHeaderScrollTreatmentStyles, U as require_jsx_runtime, Un as deriveVideoSlotPolicy, Ur as PUBLIC_BUTTON_ROLES, Ut as parseThemeForPersistence, V as bookingPaymentCollectionOptionSchema, Vr as resolveThemeColorReferenceValue, Vt as themeButtonSizeClassName, W as require_react, Wn as resolveVideoSelectionIntentForMediaField, Wr as selectLogoVariant, X as createEventCategoryRef, Xn as FILE_SIZE_LIMITS, Xr as surfaceForegroundValue, Xt as resolveItemSurface, Y as createContentEntryRef, Yn as parseRelativeStoragePath, Yr as surfaceForegroundStyle, Yt as arrangementSlotNames, Z as createEventSeriesRef, Zr as expandPalette, Zt as resolveLookSlotSurfaces, _n as summarizePortableMediaReferences, _r as defineNoBlockDesignCapability, _t as resolvePublishedLook, an as deriveSectionGround, ar as defineBlockDesignMetadata, b as isEventVisibility, bn as looksLikeUuid, br as featureCardCompositionTreatmentValues, bt as gradientToCss, c as customLinkSchema$1, cn as resolveSectionBackground, cr as cardListKnobBlocks, ct as headerLeafTextColor, d as internalLinkSchema, dn as boxBackgroundOverlaySchema, dr as asLayoutVariantKey, dt as mergeHeaderNavTypographyPreference, ei as parseBlockColorToken, er as parseSafeDownloadFilename, et as createNavigationRef, f as internalResolvedLinkSchema, fn as boxBackgroundSchema, ft as readHeaderNavFontSizePreference, g as linkSchema, gn as collectPortableMediaReferencesFromContent, gr as defineFrameCapability, gt as resolveModeArrangement, h as isResolvedInternalLinkEntityType, hn as stripStaleTransitionStyleGroup, hr as defineBlockDesignCapability, ht as resolveEffectiveArrangement, in as sectionFactsForContent, ir as normalizeCardRole, it as refSlug, j as eventManagementTiming, jn as mediaSchema, jr as resolveSurfaceForeground, jt as BREAKPOINT_SM, kn as isSiteScopedDirectImageStoragePath, kr as resolveSurface, kt as BREAKPOINT_LG, l as entryLinkSchema$1, ln as resolveSectionPhotoPlacement, lt as transparentHeaderForegroundInput, mn as siteChromeBackgroundOverrideOwner, mr as assertNever$11, mt as readStoredMode, ni as __toESM, nn as boundaryLayout, nr as cardRoleSchema, o as classifySdkEventBookingOffer, on as heroPhotoPaintsGround, or as resolveEffectiveKnobs, ot as effectiveHeaderNavLinkStyle, p as internalRouteOnlyLinkSchema, pn as runtimeBoxBackgroundSchema, pr as asSiteStyleId, pt as arrangementManifestFromVariants, q as createBlocksRef, qn as asRecord, qr as surfaceForegroundDeclarations, qt as generateNavLinkCSS, rn as composeBoundary, rr as cardVariantRoleValues, rt as createVenueRef, s as RESOLVED_INTERNAL_LINK_ENTITY_TYPES, sn as parseCanonicalRuntimeBackground, sr as cardListFamilyBlocks, st as headerForegroundCandidates, t as classifySdkEventTiming, ti as __commonJSMin, tn as headerRootPaint, tr as renderCssDeclarations, tt as createPageRef, u as externalLinkSchema$1, un as sectionForegroundBackground, ur as asDesignBlockId, ut as readOpeningForegroundProjection, v as pageLinkSchema$1, vn as walkAndCollectImageAttrs, vr as eventListTreatmentValues, vt as requireCuratedSiteStyle, w as stableStringify, wn as parseAspectRatioValue, wr as offeringsPreviewCompositionTreatmentValues, wt as themeSchema, xn as rotateNormalizedPoint, xr as headingDecorationColorTokenValues, xt as getGradientPreset, yn as deleteEnvSpecificKeys, yr as eyebrowTreatmentValues, yt as compiledRuntimeStyleFieldNames, z as eventCommandScheduleSchema, zn as parseCanonicalMediaIdentifier, zr as isCssColorLiteral, zt as parseThemeButtonVariantId } from "../sdk-event-upsert-zWw5VolM.mjs";
3
+ import { $ as resolveBookingDisplayContext, $t as assertNever, A as DEFAULT_MAILING_LIST_SUCCESS_MESSAGE, At as deriveTotalTicketQuantity, B as parseWeeklyOpeningHours, Bt as resolvePricingOptionsPriceDisplay, Ct as getBookingInstalmentDepositRefundability, D as parseMailingListSignedSourceToken, Dt as splitAmountWithRemainderInFirst, E as parseMailingListContentVersionId, En as unsafeAsOccurrenceId, Et as omitZeroAmountFutureRows, F as DEFAULT_MAILING_LIST_TARGETING, Ft as publicEventPricingCurrency, G as selectCategoryView, Gt as parseNonNegativeCents, Hn as err, I as planMailingListChoicePresentation, It as publicEventPricingDisplay, J as formatDateTime, Jt as toNonNegativeCents, K as formatDate, Kt as parsePositiveCents, L as isCanonicalRichTextDoc, Lt as publicEventPricingOptions, M as parseFooterCompositionV1, Mt as formatBookingPriceDisplay, Nt as formatEventPricing, O as parseMailingListRuntimeContext, On as unsafeAsPassId, Ot as resolveFlexibleBalanceDueAt, P as parseMailingListPlacementTargeting, Pt as isPublicEventPricingOptionDisplayable, Q as inferSiteDateLocale, Qt as parseCurrencyCode, R as formatOpeningHoursRows, Rt as publicEventPricingType, S as parseManagementKey, St as clampConfiguredAmount, T as parseMailingListBlockId, Tn as unsafeAsMediaAssetId, Tt as getBookingInstalmentProtectedDepositAmountCents, U as normalizeLocationAddressText, Un as ok, Ut as mapReadonlyNonEmptyArray, V as assertAllowedPublicUrl, Vt as fromArray, W as toLocationCoordinates, Wt as isNonNegativeWholeCents$1, X as formatTime$1, Xt as unsafeAsPositiveCents, Y as formatDateTimeRange, Yt as unsafeAsNonNegativeCents, Z as formatTimeRange, Zt as formatCurrencyAmount, _ as orderedLiveRunSessions, _t as validateBookingPaymentOptionKinds, an as parseEventSeriesId, at as getLocalScheduleDate, b as createSecret, bt as isSettleableOrderInstalmentPlanStatus, c as resolveSiteVerticalBookingConfig, ct as parseBookingLocalDate, dn as parseSiteTermsVersionId, dt as resolveBookingBusinessTimeZone, en as formatDurationUnitLabel, et as buildBookingUtcDateWindow, f as SURFACE_EVENT_PRESET_VALUES, fn as parseUserId, ft as resolveBookingTimeZoneId, g as nextLiveRunSessionStartMs, gn as unsafeAsCheckoutLineId, h as CANCELLED_SESSION_STATUS, ht as toUtcIsoDateTimeString, in as parseDatedOfferingCapacityHoldId, it as getBookingLocalDateKey, j as parseMailingListProviderScope, jn as unsafeAsPricingOptionId, jt as parseEventPageSurfaceSubject, k as assessMailingListTargeting, kn as unsafeAsPositiveByteCount, kt as resolveFlexibleBalanceInitialPaymentCents, ln as parseRetiredCourseId, lt as parseLocalScheduleTime, mt as toScheduledBookingDateTime, n as EVENT_PRESET_VOCABULARY, nn as parseCustomerId, nt as formatLocalScheduleDate, ot as getLocalScheduleTime, p as WHOLE_SERIES_EVENT_PRESET_VALUES, pt as toBookingTimeZone, q as formatDateRange, qt as toCents, r as NAVIGATION_LOCATIONS, rn as parseDatedOfferingCapacityHoldAuditRef, rt as formatLocalScheduleTime, sn as parseMembershipId, tn as parseApiKeyId, tt as compareLocalScheduleDates, u as EVENT_PRESET_VALUES, un as parseSiteId, ut as parseUtcIsoDateTimeString, v as resolveWholeSeriesBookingPhase, vn as unsafeAsDemoCapability, vt as isLiveOrderInstalmentPlanStatus, w as slugify, wt as getBookingInstalmentPlanIntervalCount, x as exposeSecret, xn as unsafeAsEventInstanceId, xt as addIntervalPreservingMonthAnchor, y as resolveWholeSeriesRunLifecycle, yt as isSettleableFlexibleBalanceContractStatus, zt as resolveExactOrFreePriceDisplay } from "../src-ZACBG29u.mjs";
4
+ import { $ as normalizeMediaValueToIdentifierOnly, $t as bindRequestTimeout, A as formatDiffDetail, An as transportSuggestion, At as navigationIdentity, B as buildFieldChanges, Bn as sdkEventScheduleSchema, Bt as preparePushConfigTarget, C as reportFromOutcomes, Cn as parseEnvironmentSelection, Ct as fileExists, D as normalizeRemoteHomepageReference, Dn as withRiverbankEnv, Dt as writeJsonFileAtomic, E as createEmptySyncResult, En as requireEnvironmentWriteAuthorization, Et as writeJsonFile, F as calculatePullDiff, Fn as isLocalConfigured, Ft as navigationCommandItemToManagementInput, G as syncLocalMediaForPull, Gt as APPOINTMENT_SERVICE_VISIBILITY_VALUES, H as formatFieldPath, Hn as linkPayloadSchema, Ht as extractApiErrorMessage, I as buildJsonDiff, In as isRemoteConfigured, It as normalizeNavigationConfigItemForCommand, J as admitBinaryWriteDestination, Jt as ApiEnvelopeError, K as applyLocalMediaPush, Kt as ApiRequestError, L as hasPageOperations, Ln as loadSdkEnvironment, Lt as applyPushConfigPlan, M as hasPendingChanges, Mt as convertPulledFooterToLocal, N as findChangedFieldsWithValues, Nn as getEnvPrefix, Nt as convertPulledNavigationToLocal, O as calculateDiff, On as ErrorCodes, Ot as isMediaValue, P as formatHumanDiff, Pn as getEnvironmentDebugInfo, Pt as convertPulledPagesToLocal, Q as normalizeMediaIdentifierInput, Qt as mapEnvironmentExecutionOutcomesJson, R as pageMetadataForUpsert, Rn as createManagementClient, Rt as createPushConfigCommand, S as planCliCommandBatchForBindings, Sn as executeEnvironmentPlan, St as ensureDir, T as executeResidualSettingsSyncPlan, Tn as planEnvironmentWriteAuthorization, Tt as readJsonFile, U as revalidateLocalMediaPushPlan, Un as portablePageDesignSchema, Ut as normalizeApiError, V as formatFieldChange, Vn as sdkSettingsUpdateSchema, Vt as revalidatePushConfigPlan, Wn as sdkConfiguredBookingVerticalConfigSchema, Wt as isRecord$2, X as writeBinaryFileAtomic, Xt as deriveCmsApiUrlFromDashboardUrl, Y as readStableBinaryFile, Yt as unwrapResponse, Z as sha256, Zt as API_ENDPOINTS, _ as commandExecutionToReport, _n as runWithInvocation, _t as metadataFilePath, a as executeEntryCommandPlanAsReport, an as createOutputForCommand, at as readEntries, b as executeCompiledCliCommandPlanAsReport, bn as withErrorHandling, bt as writeMetadataFile, c as titleFromEntryData, cn as getOutputContext, ct as readEventsMeta, d as unsafeAsCliEntryIdentifier, dn as managementClientConfigFor, dt as readNavigationMeta, en as confirmAction, et as walkLocalContentMedia, f as unsafeAsCliFormSlug, fn as mapEntryForOutput, ft as readPagesMeta, g as commandCompileFailureToReport, gn as resolveCommandPath, gt as ensureMetadataDir, h as mutationCounterForEffect, hn as parsePaginationOptions, ht as writeMediaManifest, i as entrySiteCommandTypes, in as createListCommand, it as findLegacyEntryFiles, j as formatDiffSummary, jn as CliExit, jt as navigationIdentityMap, k as calculateFooterDiff, kn as formatError, kt as duplicateNavigationIdentityMessage, l as unsafeAsCliBlockIdentifier, ln as getOutputOnlyContext, lt as readFooterMeta, m as commandReportToSyncResult, mn as parseJsonData, mt as readVenuesMeta, n as entryCommandCompileErrorToReport, nn as createCommandContext, nt as downloadMedia, o as executeEntryCommandPlanAsSyncResult, on as currentInvocationEnvelope, ot as readAllMeta, p as unsafeAsCliPageIdentifier, pn as parseJsonArray, pt as readSettingsMeta, q as prepareLocalMediaForPush, qt as createBearerAPIClient, r as entryCommandReportToSyncResult, rn as createGetCommand, rt as extractMediaIdentifiers, s as syncResultForEntryCommandCompileError, sn as formatDateShort, st as readEventCategoriesMeta, t as compileEntryPushCommands, tn as createClientForEnv, tt as buildStorageUrl, u as unsafeAsCliContentTypeKey, un as handleCommandError, ut as readFormsMeta, v as commandReportHasFailures, vn as withCommandContext, vt as metadataTargetFromEnvironment, w as mergeManySyncResults, wn as planEnvironmentExecution, wt as listFiles, x as formatManagementError, xn as withOutputOnlyErrorHandling, xt as writeMetadataRecordMap, y as executeCliCommandPlan, yn as withConfirmation, yt as readMetadataFile, zn as ManagementApiError, zt as loadPushConfigTarget } from "../entryCommands-BGVgMNMw.mjs";
5
+ import { r as CLI_VERSION, t as loadConfigFile } from "../load-config-BV9Ig2CH.mjs";
6
+ import { a as RichTextDocSchema, c as isRecord$18, i as findUnfilledSentinels, n as buildEntryScaffold, o as isRichTextDoc, r as validateEntryInstance, s as isObjectRecord, t as eventUpsertInputSchema } from "../eventUpsertInputSchema-CdX--hHz.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-CFMj_Hee.mjs";
8
8
  import { config } from "dotenv";
9
9
  import { Command, CommanderError } from "commander";
10
10
  import * as fs$2 from "fs/promises";
@@ -5996,12 +5996,13 @@ function isAllowedTransformId(id) {
5996
5996
  }
5997
5997
 
5998
5998
  //#endregion
5999
- //#region ../blocks/src/system/layout/repeaterGrid.ts
5999
+ //#region ../blocks/src/system/layout/repeaterGridModel.ts
6000
6000
  const DEFAULT_REPEATER_GRID_SPAN_FIELD_ID = "gridSpan";
6001
6001
  const DEFAULT_REPEATER_GRID_ROW_FIELD_ID = "gridRow";
6002
6002
  const DEFAULT_REPEATER_GRID_COLUMN_FIELD_ID = "gridColumn";
6003
6003
  const MIN_COLUMNS = 1;
6004
- const MAX_COLUMNS = 4;
6004
+ const MAX_REPEATER_GRID_COLUMNS = 6;
6005
+ const MAX_COLUMNS = MAX_REPEATER_GRID_COLUMNS;
6005
6006
  function normalizeInteger(value) {
6006
6007
  if (typeof value === "number" && Number.isFinite(value)) {
6007
6008
  return Math.trunc(value);
@@ -6073,7 +6074,7 @@ function createRepeaterGridSpanField(options) {
6073
6074
  const fieldId = resolveRepeaterGridSpanFieldId(options);
6074
6075
  const label = asNonEmptyString(options?.label) ?? "Grid span";
6075
6076
  const description = asNonEmptyString(options?.description) ?? "Columns occupied by this item in layout-grid mode.";
6076
- const defaultSpan = normalizeRepeaterGridColumns(options?.defaultSpan ?? 1);
6077
+ const defaultSpan = clamp(normalizeRepeaterGridColumns(options?.defaultSpan ?? 1), 1, 4);
6077
6078
  return {
6078
6079
  id: fieldId,
6079
6080
  type: "select",
@@ -6136,16 +6137,10 @@ function withRepeaterGridSpanField(fields$1, options) {
6136
6137
  return [createRepeaterGridSpanField(options), ...fields$1];
6137
6138
  }
6138
6139
  function withRepeaterGridPositionFields(fields$1, options) {
6139
- let nextFields = [...fields$1];
6140
6140
  const rowFieldId = resolveRepeaterGridRowFieldId(options?.row);
6141
- if (!nextFields.some((field) => field.id === rowFieldId)) {
6142
- nextFields = [createRepeaterGridRowField(options?.row), ...nextFields];
6143
- }
6141
+ const withRow = fields$1.some((field) => field.id === rowFieldId) ? [...fields$1] : [createRepeaterGridRowField(options?.row), ...fields$1];
6144
6142
  const columnFieldId = resolveRepeaterGridColumnFieldId(options?.column);
6145
- if (!nextFields.some((field) => field.id === columnFieldId)) {
6146
- nextFields = [createRepeaterGridColumnField(options?.column), ...nextFields];
6147
- }
6148
- return nextFields;
6143
+ return withRow.some((field) => field.id === columnFieldId) ? withRow : [createRepeaterGridColumnField(options?.column), ...withRow];
6149
6144
  }
6150
6145
  function withRepeaterGridSpanFieldOnItemTypes(itemTypes, options) {
6151
6146
  const entries = Object.entries(itemTypes).map(([typeKey, itemType]) => {
@@ -6192,7 +6187,26 @@ function resolveRepeaterGridColumnsForBreakpoint(values, bindings, breakpoint) {
6192
6187
  }
6193
6188
  return MIN_COLUMNS;
6194
6189
  }
6195
- function normalizeRepeaterGridSpan(value, activeColumns) {
6190
+ /** Strict admission; rendering normalization never grants permission to persist. */
6191
+ function parseRepeaterGridInteger(value) {
6192
+ if (typeof value !== "number" && (typeof value !== "string" || !value.trim())) return null;
6193
+ const number = Number(value);
6194
+ return Number.isInteger(number) ? number : null;
6195
+ }
6196
+ function isAcceptedRepeaterGridSpan(value, columns, acceptedSpans$1) {
6197
+ const span = parseRepeaterGridInteger(value);
6198
+ return span != null && span >= 1 && span <= columns && (!acceptedSpans$1 || acceptedSpans$1.includes(span));
6199
+ }
6200
+ function resolveRepeaterGridAcceptedSpans(fields$1, spanField) {
6201
+ const field = fields$1.find((candidate) => candidate.id === spanField);
6202
+ return field?.type === "select" ? (field.options ?? []).map((option) => Number(option.value)) : undefined;
6203
+ }
6204
+ function normalizeRepeaterGridSpan(value, activeColumns, acceptedSpans$1) {
6205
+ if (acceptedSpans$1) {
6206
+ const valid = acceptedSpans$1.filter((span) => span <= activeColumns);
6207
+ const parsed$1 = parseRepeaterGridInteger(value);
6208
+ return parsed$1 != null && valid.includes(parsed$1) ? parsed$1 : valid.length ? Math.min(...valid) : 1;
6209
+ }
6196
6210
  const parsed = normalizeInteger(value);
6197
6211
  if (parsed == null) {
6198
6212
  return 1;
@@ -6237,12 +6251,14 @@ function getOverlappingItemIds(input) {
6237
6251
  return new Set(buildPlacementCells(input).flatMap((col) => Array.from(input.occupancy.get(toRowCellKey(input.row, col, input.columns)) ?? []).filter((id) => !input.excludedIds?.has(id))));
6238
6252
  }
6239
6253
  function normalizeInputItem(item, columns, sourceIndex) {
6240
- const span = normalizeRepeaterGridSpan(item.span, columns);
6254
+ const span = normalizeRepeaterGridSpan(item.span, columns, item.acceptedSpans);
6241
6255
  const row$1 = normalizeRepeaterGridRow(item.row);
6242
6256
  const colStart = normalizeRepeaterGridColStart(item.colStart, columns, span);
6243
6257
  const colEnd = colStart + span - 1;
6244
6258
  return {
6245
6259
  id: item.id,
6260
+ startNewRow: item.startNewRow,
6261
+ acceptedSpans: item.acceptedSpans,
6246
6262
  sourceIndex,
6247
6263
  span,
6248
6264
  row: row$1,
@@ -6251,8 +6267,134 @@ function normalizeInputItem(item, columns, sourceIndex) {
6251
6267
  startCell: toRowCellKey(row$1, colStart, columns)
6252
6268
  };
6253
6269
  }
6270
+ /** Ordered, non-dense placement. Rendering clamps widths; admission checks originals separately. */
6271
+ function flowRepeaterGridItems(items, columns) {
6272
+ const count = normalizeRepeaterGridColumns(columns);
6273
+ return items.reduce((placed, item) => {
6274
+ const span = normalizeRepeaterGridSpan(item.span, count);
6275
+ const previous = placed.at(-1);
6276
+ const nextColumn = previous ? previous.colStart + previous.span : 1;
6277
+ const wraps = previous != null && (item.startNewRow === true || nextColumn + span - 1 > count);
6278
+ return [...placed, {
6279
+ ...item,
6280
+ span,
6281
+ row: (previous?.row ?? 1) + (wraps ? 1 : 0),
6282
+ colStart: wraps ? 1 : nextColumn
6283
+ }];
6284
+ }, []);
6285
+ }
6286
+ /** @internal */
6287
+ function orderedGridItems(layout) {
6288
+ return [...layout.placements].sort((a$1, b$1) => a$1.sourceIndex - b$1.sourceIndex);
6289
+ }
6290
+ function isRepeaterGridLayoutInvalid(layout) {
6291
+ return layout.hasInvalidItems || layout.hasCollisions || layout.overCapacity;
6292
+ }
6293
+ function hasInvalidGridItems(items, columns, policy) {
6294
+ const flow = policy === "flow" ? flowRepeaterGridItems(items, columns) : null;
6295
+ return items.some((item, index) => !isValidGridItemGeometry(item, columns) || flow != null && (item.row !== flow[index]?.row || item.colStart !== flow[index]?.colStart));
6296
+ }
6297
+ function isValidGridItemGeometry(item, columns) {
6298
+ return isAcceptedRepeaterGridSpan(item.span, columns, item.acceptedSpans) && parseRepeaterGridInteger(item.row) != null && item.row >= 1 && parseRepeaterGridInteger(item.colStart) != null && item.colStart >= 1 && item.colStart + item.span - 1 <= columns;
6299
+ }
6300
+ function findFlowAppendPlacement(layout, span, startNewRow) {
6301
+ const next = flowRepeaterGridItems([...orderedGridItems(layout), {
6302
+ id: "new",
6303
+ span,
6304
+ row: 1,
6305
+ colStart: 1,
6306
+ startNewRow
6307
+ }], layout.columns).at(-1);
6308
+ return !isRepeaterGridLayoutInvalid(layout) && next && (layout.maxRows == null || next.row <= layout.maxRows) ? {
6309
+ row: next.row,
6310
+ colStart: next.colStart
6311
+ } : null;
6312
+ }
6313
+ /** @internal */
6314
+ function planFlowItems(layout, items) {
6315
+ const placed = flowRepeaterGridItems(items, layout.columns);
6316
+ if (isRepeaterGridLayoutInvalid(layout) || placed.some((item) => !isAcceptedRepeaterGridSpan(item.span, layout.columns, item.acceptedSpans) || layout.maxRows != null && item.row > layout.maxRows)) return { kind: "blocked" };
6317
+ const [first, ...rest] = placed.map(({ id, row: row$1, colStart, span }) => ({
6318
+ id,
6319
+ row: row$1,
6320
+ colStart,
6321
+ span
6322
+ }));
6323
+ return first ? {
6324
+ kind: "move",
6325
+ updates: [first, ...rest]
6326
+ } : { kind: "unchanged" };
6327
+ }
6328
+ /** Insertion reorder, with a complete coordinate write for the resulting order. */
6329
+ function planRepeaterGridReorder(layout, id, to, interaction = "step") {
6330
+ if (layout.placementPolicy !== "flow") return { kind: "blocked" };
6331
+ const items = orderedGridItems(layout);
6332
+ const from = items.findIndex((item) => item.id === id);
6333
+ if (from < 0 || to < 0 || to >= items.length) return { kind: "blocked" };
6334
+ if (from === to) return { kind: "unchanged" };
6335
+ const active = items[from];
6336
+ if (!active) return { kind: "blocked" };
6337
+ return decideFlowReorder({
6338
+ layout,
6339
+ items,
6340
+ active,
6341
+ from,
6342
+ to,
6343
+ interaction
6344
+ });
6345
+ }
6346
+ function decideFlowReorder(input) {
6347
+ const { layout, items, active, from, to, interaction } = input;
6348
+ const id = active.id;
6349
+ const remaining = items.filter((item) => item.id !== id);
6350
+ const inserted = [
6351
+ ...remaining.slice(0, to),
6352
+ active,
6353
+ ...remaining.slice(to)
6354
+ ];
6355
+ const clearRowBreak = interaction === "drag" && active.startNewRow === true && dragInsertsWithinRow(items, from, to, layout.columns);
6356
+ const nextItems = clearRowBreak ? inserted.map((item) => item.id === id ? {
6357
+ ...item,
6358
+ startNewRow: false
6359
+ } : item) : inserted;
6360
+ const decision = planFlowItems(layout, nextItems);
6361
+ return decision.kind === "move" ? {
6362
+ ...decision,
6363
+ reorder: {
6364
+ id,
6365
+ from,
6366
+ to
6367
+ },
6368
+ ...clearRowBreak ? { startNewRowUpdate: {
6369
+ id,
6370
+ value: false
6371
+ } } : {}
6372
+ } : decision;
6373
+ }
6374
+ /** Read the insertion bar position, independently of the dragged width. */
6375
+ function dragInsertsWithinRow(items, from, to, columns) {
6376
+ const target = items[to];
6377
+ if (!target) return false;
6378
+ const column = from < to ? target.colEnd + 1 : target.colStart;
6379
+ return column > 1 && column <= columns;
6380
+ }
6381
+ function planRepeaterGridStartNewRow(layout, id, value) {
6382
+ const active = layout.byId[id];
6383
+ if (layout.placementPolicy !== "flow" || !active) return { kind: "blocked" };
6384
+ const decision = planFlowItems(layout, orderedGridItems(layout).map((item) => item.id === id ? {
6385
+ ...item,
6386
+ startNewRow: value
6387
+ } : item));
6388
+ return decision.kind === "move" ? {
6389
+ ...decision,
6390
+ startNewRowUpdate: {
6391
+ id,
6392
+ value
6393
+ }
6394
+ } : decision;
6395
+ }
6254
6396
  function buildRepeaterGridLayout(input) {
6255
- const columns = clamp(Math.max(1, Math.trunc(input.columns)), MIN_COLUMNS, MAX_COLUMNS);
6397
+ const columns = normalizeRepeaterGridColumns(input.columns);
6256
6398
  const maxRows = input.maxRows != null ? Math.max(1, Math.trunc(input.maxRows)) : undefined;
6257
6399
  const capacityCells = maxRows != null ? maxRows * columns : undefined;
6258
6400
  const placements = input.items.map((item, sourceIndex) => normalizeInputItem(item, columns, sourceIndex)).sort((a$1, b$1) => {
@@ -6265,55 +6407,58 @@ function buildRepeaterGridLayout(input) {
6265
6407
  const { hasCollisions } = buildOccupancyByCell(placements, columns);
6266
6408
  const overCapacity = maxRows != null && maxRowUsed > maxRows;
6267
6409
  return {
6410
+ placementPolicy: input.placementPolicy,
6411
+ sourceItems: input.items.map((item) => ({ ...item })),
6268
6412
  columns,
6269
6413
  maxRows,
6270
6414
  capacityCells,
6271
6415
  maxRowUsed,
6272
6416
  overCapacity,
6273
6417
  hasCollisions,
6418
+ hasInvalidItems: hasInvalidGridItems(input.items, columns, input.placementPolicy),
6274
6419
  placements,
6275
6420
  byId
6276
6421
  };
6277
6422
  }
6278
6423
  function findFirstAvailableRepeaterGridPlacement(input) {
6279
- const columns = clamp(Math.max(1, Math.trunc(input.columns)), MIN_COLUMNS, MAX_COLUMNS);
6424
+ const columns = normalizeRepeaterGridColumns(input.columns);
6280
6425
  const maxRows = input.maxRows != null ? Math.max(1, Math.trunc(input.maxRows)) : undefined;
6281
- const span = normalizeRepeaterGridSpan(input.newItemSpan, columns);
6426
+ if (!isAcceptedRepeaterGridSpan(input.newItemSpan, columns, input.acceptedSpans)) return null;
6427
+ const span = normalizeRepeaterGridSpan(input.newItemSpan, columns, input.acceptedSpans);
6282
6428
  const layout = buildRepeaterGridLayout({
6283
6429
  items: input.items,
6284
6430
  columns,
6285
- maxRows
6431
+ maxRows,
6432
+ placementPolicy: input.placementPolicy
6286
6433
  });
6434
+ if (input.placementPolicy === "flow") return findFlowAppendPlacement(layout, span, input.startNewRow);
6287
6435
  const { occupancy } = buildOccupancyByCell(layout.placements, columns);
6288
6436
  const rowLimit = maxRows ?? Math.max(1, layout.maxRowUsed + 1);
6289
- for (let row$1 = 1; row$1 <= rowLimit; row$1 += 1) {
6290
- const maxColStart = Math.max(1, columns - span + 1);
6291
- for (let colStart = 1; colStart <= maxColStart; colStart += 1) {
6292
- const overlaps = getOverlappingItemIds({
6293
- occupancy,
6294
- row: row$1,
6295
- colStart,
6296
- span,
6297
- columns
6298
- });
6299
- if (overlaps.size === 0) {
6300
- return {
6301
- row: row$1,
6302
- colStart
6303
- };
6304
- }
6305
- }
6306
- }
6307
- return null;
6437
+ if (isRepeaterGridLayoutInvalid(layout)) return null;
6438
+ const maxColStart = columns - span + 1;
6439
+ return Array.from({ length: rowLimit * maxColStart }, (_$1, index) => ({
6440
+ row: Math.floor(index / maxColStart) + 1,
6441
+ colStart: index % maxColStart + 1
6442
+ })).find(({ row: row$1, colStart }) => getOverlappingItemIds({
6443
+ occupancy,
6444
+ row: row$1,
6445
+ colStart,
6446
+ span,
6447
+ columns
6448
+ }).size === 0) ?? null;
6308
6449
  }
6309
6450
  function canAppendRepeaterGridItem(input) {
6310
6451
  return findFirstAvailableRepeaterGridPlacement({
6311
6452
  items: input.items,
6312
6453
  newItemSpan: input.newItemSpan,
6454
+ acceptedSpans: input.acceptedSpans,
6313
6455
  columns: input.columns,
6314
6456
  maxRows: input.maxRows
6315
6457
  }) != null;
6316
6458
  }
6459
+
6460
+ //#endregion
6461
+ //#region ../blocks/src/system/layout/repeaterGrid.ts
6317
6462
  function placementsOverlap(left, right) {
6318
6463
  return left.row === right.row && left.colStart < right.colStart + right.span && right.colStart < left.colStart + left.span;
6319
6464
  }
@@ -6333,10 +6478,44 @@ function swapPositionUpdates(active, target) {
6333
6478
  span: target.span
6334
6479
  }];
6335
6480
  }
6481
+ /** Admission retains raw authoring geometry; display-only callers may supply placements alone. */
6482
+ function sourceGridItems(layout) {
6483
+ return layout.sourceItems ?? layout.placements;
6484
+ }
6485
+ /** Repair edits may remove existing defects, but cannot introduce another one. */
6486
+ function gridDefects(layout) {
6487
+ const geometry = sourceGridItems(layout).flatMap((item) => [
6488
+ ...!isAcceptedRepeaterGridSpan(item.span, layout.columns, item.acceptedSpans) ? [`span:${item.id}`] : [],
6489
+ ...!Number.isInteger(item.row) || item.row < 1 ? [`row:${item.id}`] : [],
6490
+ ...!Number.isInteger(item.colStart) || item.colStart < 1 || item.colStart + item.span - 1 > layout.columns ? [`column:${item.id}`] : []
6491
+ ]);
6492
+ const collisions = layout.placements.flatMap((item, index) => layout.placements.slice(index + 1).filter((other) => placementsOverlap(item, other)).map((other) => `collision:${[item.id, other.id].sort().join(":")}`));
6493
+ const capacity = layout.placements.filter((item) => layout.maxRows != null && item.row > layout.maxRows).map((item) => `capacity:${item.id}`);
6494
+ return new Set([
6495
+ ...geometry,
6496
+ ...collisions,
6497
+ ...capacity,
6498
+ ...(layout.invalidCoordinateIds ?? []).map((id) => `coordinate:${id}`)
6499
+ ]);
6500
+ }
6336
6501
  function validateGridMove(layout, updates) {
6337
- const moved = updates;
6338
- const stationary = layout.placements.filter((placement) => !updates.some((update) => update.id === placement.id));
6339
- const valid = moved.every((placement, index) => placement.colStart >= 1 && placement.colStart + placement.span - 1 <= layout.columns && placement.row >= 1 && (layout.maxRows == null || placement.row <= layout.maxRows) && !stationary.some((other) => placementsOverlap(placement, other)) && !moved.slice(index + 1).some((other) => placementsOverlap(placement, other)));
6502
+ const items = sourceGridItems(layout).map((item) => ({
6503
+ ...item,
6504
+ ...updates.find((update) => update.id === item.id)
6505
+ }));
6506
+ const candidate = buildRepeaterGridLayout({
6507
+ items,
6508
+ columns: layout.columns,
6509
+ maxRows: layout.maxRows,
6510
+ placementPolicy: layout.placementPolicy
6511
+ });
6512
+ const unresolved = (layout.invalidCoordinateIds ?? []).filter((id) => !updates.some((update) => update.id === id));
6513
+ const before = gridDefects(layout);
6514
+ const after = gridDefects({
6515
+ ...candidate,
6516
+ invalidCoordinateIds: unresolved
6517
+ });
6518
+ const valid = before.size === 0 ? after.size === 0 : after.size < before.size && [...after].every((defect) => before.has(defect));
6340
6519
  return valid ? {
6341
6520
  kind: "move",
6342
6521
  updates
@@ -6347,7 +6526,7 @@ function decideGridDrop(layout, active, destination) {
6347
6526
  if (occupants.length > 1) return { kind: "blocked" };
6348
6527
  const target = occupants[0];
6349
6528
  if (target) return validateGridMove(layout, swapPositionUpdates(active, target));
6350
- if (destination.row === active.row && destination.colStart === active.colStart) return { kind: "unchanged" };
6529
+ if (destination.row === active.row && destination.colStart === active.colStart && !isRepeaterGridLayoutInvalid(layout)) return { kind: "unchanged" };
6351
6530
  return validateGridMove(layout, [{
6352
6531
  id: active.id,
6353
6532
  row: destination.row,
@@ -6358,9 +6537,16 @@ function decideGridDrop(layout, active, destination) {
6358
6537
  function buildRepeaterGridDropTargets(input) {
6359
6538
  const { layout, draggedId, rowsToRender } = input;
6360
6539
  const active = layout.byId[draggedId];
6361
- if (!active) return [];
6540
+ if (!active || layout.placementPolicy === "flow" && isRepeaterGridLayoutInvalid(layout)) return [];
6541
+ if (layout.placementPolicy === "flow") return orderedGridItems(layout).map((item, index) => ({
6542
+ id: `repeater-grid-drop-${item.row}-${item.colStart}`,
6543
+ row: item.row,
6544
+ colStart: item.colStart,
6545
+ cell: item.startCell,
6546
+ decision: planRepeaterGridReorder(layout, draggedId, index, input.interaction ?? "drag")
6547
+ }));
6362
6548
  const rowCeiling = layout.maxRows ?? Math.max(1, rowsToRender ?? layout.maxRowUsed + 1, active.row + 1);
6363
- return Array.from({ length: rowCeiling * layout.columns }, (_$1, index) => {
6549
+ const targets = Array.from({ length: rowCeiling * layout.columns }, (_$1, index) => {
6364
6550
  const row$1 = Math.floor(index / layout.columns) + 1;
6365
6551
  const colStart = index % layout.columns + 1;
6366
6552
  return {
@@ -6374,6 +6560,205 @@ function buildRepeaterGridDropTargets(input) {
6374
6560
  })
6375
6561
  };
6376
6562
  });
6563
+ return targets.some((target) => target.row === active.row && target.colStart === active.colStart) ? targets : [...targets, {
6564
+ id: `repeater-grid-drop-${active.row}-${active.colStart}`,
6565
+ row: active.row,
6566
+ colStart: active.colStart,
6567
+ cell: active.startCell,
6568
+ decision: { kind: "unchanged" }
6569
+ }];
6570
+ }
6571
+ /** Width edits keep the item's start when it fits; otherwise relocate it to the first free cell.
6572
+ * Neighbours keep their positions. A full bounded grid blocks the change without partial writes.
6573
+ */
6574
+ function planRepeaterGridSpanChange(input) {
6575
+ const { layout, id, span } = input;
6576
+ const active = layout.byId[id];
6577
+ if (layout.placementPolicy === "flow" && isRepeaterGridLayoutInvalid(layout)) return { kind: "blocked" };
6578
+ if (!active || !isAcceptedRepeaterGridSpan(span, layout.columns, active.acceptedSpans)) return { kind: "blocked" };
6579
+ const nextSpan = Number(span);
6580
+ if (!isRepeaterGridLayoutInvalid(layout) && sourceGridItems(layout).find((item) => item.id === id)?.span === nextSpan) return { kind: "unchanged" };
6581
+ if (layout.placementPolicy === "flow") return planFlowItems(layout, orderedGridItems(layout).map((item) => item.id === id ? {
6582
+ ...item,
6583
+ span: nextSpan
6584
+ } : item));
6585
+ return planFreeGridSpanChange(layout, active, nextSpan);
6586
+ }
6587
+ /** Free width edits retain the current start when possible, then use the shared first-free planner. */
6588
+ function planFreeGridSpanChange(layout, active, nextSpan) {
6589
+ const id = active.id;
6590
+ const atCurrent = validateGridMove(layout, [{
6591
+ id,
6592
+ span: nextSpan,
6593
+ row: active.row,
6594
+ colStart: active.colStart
6595
+ }]);
6596
+ if (atCurrent.kind === "move") return atCurrent;
6597
+ const destination = findFirstAvailableRepeaterGridPlacement({
6598
+ items: layout.placements.filter((item) => item.id !== id),
6599
+ newItemSpan: nextSpan,
6600
+ acceptedSpans: active.acceptedSpans,
6601
+ columns: layout.columns,
6602
+ maxRows: layout.maxRows
6603
+ });
6604
+ return destination ? validateGridMove(layout, [{
6605
+ id,
6606
+ span: nextSpan,
6607
+ ...destination
6608
+ }]) : { kind: "blocked" };
6609
+ }
6610
+ /** Narrow rendering is a projection only. Never feed these placements into an authoring write. */
6611
+ function projectRepeaterGridLayout(layout, columns) {
6612
+ if (isRepeaterGridLayoutInvalid(layout)) return projectRepeaterGridLayoutForRendering(layout, columns);
6613
+ if (columns === layout.columns) return layout;
6614
+ if (layout.placementPolicy === "flow") return buildProjectedRepeaterGridLayout(layout, flowRepeaterGridItems(orderedGridItems(layout), columns), columns);
6615
+ const items = layout.placements.reduce((placed, item) => {
6616
+ const span = normalizeRepeaterGridSpan(item.span, columns);
6617
+ const destination = findFirstAvailableRepeaterGridPlacement({
6618
+ items: placed,
6619
+ newItemSpan: span,
6620
+ columns
6621
+ });
6622
+ return [...placed, {
6623
+ id: item.id,
6624
+ span,
6625
+ row: destination?.row ?? placed.length + 1,
6626
+ colStart: destination?.colStart ?? 1
6627
+ }];
6628
+ }, []);
6629
+ return buildProjectedRepeaterGridLayout(layout, items, columns);
6630
+ }
6631
+ function buildProjectedRepeaterGridLayout(source, items, columns) {
6632
+ const projected = buildRepeaterGridLayout({
6633
+ items: items.map(({ id, span, row: row$1, colStart, startNewRow }) => ({
6634
+ id,
6635
+ span,
6636
+ row: row$1,
6637
+ colStart,
6638
+ startNewRow
6639
+ })),
6640
+ columns,
6641
+ placementPolicy: source.placementPolicy === "flow" || isRepeaterGridLayoutInvalid(source) ? "flow" : source.placementPolicy
6642
+ });
6643
+ const placements = projected.placements.map((item) => ({
6644
+ ...item,
6645
+ sourceIndex: source.byId[item.id]?.sourceIndex ?? item.sourceIndex
6646
+ }));
6647
+ return {
6648
+ ...projected,
6649
+ placements,
6650
+ byId: Object.fromEntries(placements.map((item) => [item.id, item]))
6651
+ };
6652
+ }
6653
+ /** Public fallback: preserve saved array order, use non-dense auto-placement, and ignore authoring capacity. */
6654
+ function projectRepeaterGridLayoutForRendering(layout, columns = layout.columns) {
6655
+ if (!isRepeaterGridLayoutInvalid(layout)) {
6656
+ return projectRepeaterGridLayout(layout, columns);
6657
+ }
6658
+ const count = normalizeRepeaterGridColumns(columns);
6659
+ return buildProjectedRepeaterGridLayout(layout, flowRepeaterGridItems(orderedGridItems(layout), count), count);
6660
+ }
6661
+ /** An explicit column reduction re-places free grids through the existing placement primitive. */
6662
+ function planRepeaterGridColumnChange(layout, columns) {
6663
+ if (layout.placementPolicy === "flow" || columns >= layout.columns) return { kind: "unchanged" };
6664
+ if (isRepeaterGridLayoutInvalid(layout)) return { kind: "blocked" };
6665
+ const placed = orderedGridItems(layout).reduce((items, item) => {
6666
+ if (!items) return null;
6667
+ const bounded = normalizeRepeaterGridSpan(item.span, columns);
6668
+ const span = item.acceptedSpans ? Math.max(0, ...item.acceptedSpans.filter((width) => width <= bounded)) : bounded;
6669
+ const destination = findFirstAvailableRepeaterGridPlacement({
6670
+ items,
6671
+ columns,
6672
+ maxRows: layout.maxRows,
6673
+ newItemSpan: span,
6674
+ acceptedSpans: item.acceptedSpans
6675
+ });
6676
+ return destination ? [...items, {
6677
+ ...item,
6678
+ span,
6679
+ ...destination
6680
+ }] : null;
6681
+ }, []);
6682
+ const [first, ...rest] = placed ?? [];
6683
+ return first ? {
6684
+ kind: "move",
6685
+ updates: [first, ...rest]
6686
+ } : placed ? { kind: "unchanged" } : { kind: "blocked" };
6687
+ }
6688
+
6689
+ //#endregion
6690
+ //#region ../blocks/src/system/manifest/layoutGridConfiguration.ts
6691
+ const itemFieldsSchema = z.object({ fields: z.array(z.object({
6692
+ id: z.string(),
6693
+ type: z.string(),
6694
+ defaultValue: z.unknown().optional(),
6695
+ options: z.array(z.object({ value: z.string() })).optional(),
6696
+ multiple: z.boolean().optional(),
6697
+ source: z.object({ kind: z.string() }).optional()
6698
+ })) });
6699
+ function validateSpanSelect(span, columns, ctx) {
6700
+ if (span.type !== "select") return;
6701
+ const options = span.options ?? [];
6702
+ const invalidOptions = options.some((option) => !isAcceptedRepeaterGridSpan(option.value, columns) || String(Number(option.value)) !== option.value);
6703
+ if (span.multiple || options.length === 0 || invalidOptions || span.source && span.source.kind !== "static") {
6704
+ ctx.addIssue({
6705
+ code: "custom",
6706
+ path: ["ui", "layoutGridSpanField"],
6707
+ message: `Layout-grid span select must have single, static integer options within 1..${columns}`
6708
+ });
6709
+ }
6710
+ if (span.defaultValue !== undefined && !options.some((option) => option.value === span.defaultValue)) {
6711
+ ctx.addIssue({
6712
+ code: "custom",
6713
+ path: ["ui", "layoutGridSpanField"],
6714
+ message: "Layout-grid default span must be one of the span select options"
6715
+ });
6716
+ }
6717
+ }
6718
+ /** Both monomorphic and polymorphic item schemas have the same layout contract. */
6719
+ function validateLayoutGridConfiguration(data, ctx) {
6720
+ const ui = data.ui;
6721
+ if (ui?.repeaterPreset !== "layoutGrid") return;
6722
+ if (ui.layoutGridColumns !== undefined === (ui.layoutGridColumnsFields !== undefined)) {
6723
+ ctx.addIssue({
6724
+ code: "custom",
6725
+ path: ["ui", "layoutGridColumns"],
6726
+ message: "Layout-grid requires exactly one of ui.layoutGridColumns or ui.layoutGridColumnsFields.lg; do not set both"
6727
+ });
6728
+ }
6729
+ const spanId = resolveRepeaterGridSpanFieldId({ id: ui.layoutGridSpanField });
6730
+ const requiredIds = [
6731
+ spanId,
6732
+ resolveRepeaterGridRowFieldId({ id: ui.layoutGridRowField }),
6733
+ resolveRepeaterGridColumnFieldId({ id: ui.layoutGridColumnField })
6734
+ ];
6735
+ const schemas = data.polymorphic && data.itemTypes ? Object.values(data.itemTypes) : [data.schema];
6736
+ schemas.forEach((schema) => {
6737
+ const parsed = itemFieldsSchema.safeParse(schema);
6738
+ if (!parsed.success) return;
6739
+ if (!requiredIds.every((id) => parsed.data.fields.some((field) => field.id === id))) {
6740
+ ctx.addIssue({
6741
+ code: "custom",
6742
+ path: ["ui", "layoutGridRowField"],
6743
+ message: `layoutGrid repeaters require span/row/column fields (${requiredIds.join(", ")}) to exist in repeater item fields`
6744
+ });
6745
+ }
6746
+ if (ui.layoutGridStartNewRowField && !parsed.data.fields.some((field) => field.id === ui.layoutGridStartNewRowField && field.type === "boolean")) {
6747
+ ctx.addIssue({
6748
+ code: "custom",
6749
+ path: ["ui", "layoutGridStartNewRowField"],
6750
+ message: "Layout-grid Start a new row field must reference a boolean item field"
6751
+ });
6752
+ }
6753
+ if (ui.layoutGridStartNewRowField && ui.layoutGridColumns === undefined) {
6754
+ ctx.addIssue({
6755
+ code: "custom",
6756
+ path: ["ui", "layoutGridStartNewRowField"],
6757
+ message: "Layout-grid Start a new row requires fixed columns (flow placement)"
6758
+ });
6759
+ }
6760
+ parsed.data.fields.filter((field) => field.id === spanId).forEach((span) => validateSpanSelect(span, ui.layoutGridColumns ?? MAX_REPEATER_GRID_COLUMNS, ctx));
6761
+ });
6377
6762
  }
6378
6763
 
6379
6764
  //#endregion
@@ -6560,6 +6945,14 @@ const uiSchema = z.object({
6560
6945
  thumbnailLabelField: z.string().optional(),
6561
6946
  thumbnailAspectRatio: z.string().optional(),
6562
6947
  thumbnailCardDensity: z.enum(["compact", "comfortable"]).optional(),
6948
+ layoutGridColumns: z.union([
6949
+ z.literal(1),
6950
+ z.literal(2),
6951
+ z.literal(3),
6952
+ z.literal(4),
6953
+ z.literal(5),
6954
+ z.literal(6)
6955
+ ]).optional(),
6563
6956
  layoutGridColumnsFields: z.object({
6564
6957
  base: z.string().optional(),
6565
6958
  sm: z.string().optional(),
@@ -6568,6 +6961,7 @@ const uiSchema = z.object({
6568
6961
  xl: z.string().optional()
6569
6962
  }).optional(),
6570
6963
  layoutGridSpanField: z.string().optional(),
6964
+ layoutGridStartNewRowField: z.string().optional(),
6571
6965
  layoutGridRowField: z.string().optional(),
6572
6966
  layoutGridColumnField: z.string().optional(),
6573
6967
  layoutGridMaxRows: z.number().int().positive().optional(),
@@ -6589,7 +6983,26 @@ const uiSchema = z.object({
6589
6983
  "style"
6590
6984
  ]).optional(),
6591
6985
  valueContract: z.enum(["boxBackground"]).optional()
6592
- }).partial();
6986
+ }).partial().superRefine((ui, ctx) => {
6987
+ if (ui.layoutGridColumns !== undefined && ui.layoutGridColumnsFields !== undefined) {
6988
+ ctx.addIssue({
6989
+ code: "custom",
6990
+ path: ["layoutGridColumns"],
6991
+ message: "Layout-grid requires exactly one of ui.layoutGridColumns or ui.layoutGridColumnsFields.lg; do not set both"
6992
+ });
6993
+ }
6994
+ }).transform((input) => {
6995
+ const { layoutGridColumns, layoutGridColumnsFields, ...ui } = input;
6996
+ if (layoutGridColumns !== undefined) return {
6997
+ ...ui,
6998
+ layoutGridColumns
6999
+ };
7000
+ if (layoutGridColumnsFields !== undefined) return {
7001
+ ...ui,
7002
+ layoutGridColumnsFields
7003
+ };
7004
+ return ui;
7005
+ });
6593
7006
  const baseFieldSchema = z.object({
6594
7007
  id: z.string().min(1, "Field id is required"),
6595
7008
  label: z.string().min(1, "Field label is required"),
@@ -6775,44 +7188,7 @@ const repeaterFieldSchema = baseFieldSchema.extend({
6775
7188
  const hasSchema = data.schema !== undefined;
6776
7189
  const hasPolymorphic = data.polymorphic === true && data.itemTypes !== undefined;
6777
7190
  return hasSchema !== hasPolymorphic;
6778
- }, { message: "Repeater must have either 'schema' (monomorphic) or 'polymorphic: true' with 'itemTypes' (polymorphic)" }).refine((data) => {
6779
- if (data.ui?.repeaterPreset !== "layoutGrid") {
6780
- return true;
6781
- }
6782
- const columnsFields = data.ui.layoutGridColumnsFields;
6783
- return !!columnsFields?.lg;
6784
- }, {
6785
- message: "ui.layoutGridColumnsFields.lg is required when ui.repeaterPreset is 'layoutGrid'",
6786
- path: [
6787
- "ui",
6788
- "layoutGridColumnsFields",
6789
- "lg"
6790
- ]
6791
- }).refine((data) => {
6792
- if (data.ui?.repeaterPreset !== "layoutGrid") {
6793
- return true;
6794
- }
6795
- const spanField = resolveRepeaterGridSpanFieldId({ id: typeof data.ui.layoutGridSpanField === "string" ? data.ui.layoutGridSpanField : undefined });
6796
- const rowField = resolveRepeaterGridRowFieldId({ id: typeof data.ui.layoutGridRowField === "string" ? data.ui.layoutGridRowField : undefined });
6797
- const columnField = resolveRepeaterGridColumnFieldId({ id: typeof data.ui.layoutGridColumnField === "string" ? data.ui.layoutGridColumnField : undefined });
6798
- const requiredFieldIds = [
6799
- spanField,
6800
- rowField,
6801
- columnField
6802
- ];
6803
- if (data.polymorphic && data.itemTypes) {
6804
- const itemTypes = Object.values(data.itemTypes);
6805
- return itemTypes.every((itemType) => Array.isArray(itemType.fields) && requiredFieldIds.every((requiredId) => itemType.fields?.some((field) => field.id === requiredId)));
6806
- }
6807
- const schemaFields = data.schema?.fields;
6808
- if (!schemaFields) {
6809
- return true;
6810
- }
6811
- return requiredFieldIds.every((requiredId) => schemaFields.some((field) => field.id === requiredId));
6812
- }, {
6813
- message: `layoutGrid repeaters require span/row/column fields (defaults: '${DEFAULT_REPEATER_GRID_SPAN_FIELD_ID}', '${DEFAULT_REPEATER_GRID_ROW_FIELD_ID}', '${DEFAULT_REPEATER_GRID_COLUMN_FIELD_ID}') to exist in repeater item fields`,
6814
- path: ["ui", "layoutGridRowField"]
6815
- }).refine((data) => {
7191
+ }, { message: "Repeater must have either 'schema' (monomorphic) or 'polymorphic: true' with 'itemTypes' (polymorphic)" }).superRefine(validateLayoutGridConfiguration).refine((data) => {
6816
7192
  const thumbnailAspectRatio = data.ui?.thumbnailAspectRatio;
6817
7193
  if (!thumbnailAspectRatio) {
6818
7194
  return true;
@@ -7144,6 +7520,195 @@ const manifestSchema = z.object({
7144
7520
  icon: z.string().optional()
7145
7521
  });
7146
7522
 
7523
+ //#endregion
7524
+ //#region ../blocks/src/lib/media.ts
7525
+ const MEDIA_TYPE_DISCRIMINANTS = mediaTypeDiscriminants;
7526
+ const PUBLIC_DOWNLOAD_MEDIA_TYPES = [
7527
+ "document",
7528
+ "spreadsheet",
7529
+ "archive",
7530
+ "audio"
7531
+ ];
7532
+ function isMediaTypeDiscriminant(value) {
7533
+ return typeof value === "string" && MEDIA_TYPE_DISCRIMINANTS.includes(value);
7534
+ }
7535
+ function mediaTypeDiscriminantFromMimeType(mimeType) {
7536
+ const classified = classifyKnownMimeType(mimeType);
7537
+ if (!classified.ok) return null;
7538
+ switch (classified.value.tag) {
7539
+ case "image":
7540
+ case "video":
7541
+ case "audio":
7542
+ case "document":
7543
+ case "spreadsheet":
7544
+ case "archive": return classified.value.tag;
7545
+ case "font": return null;
7546
+ }
7547
+ }
7548
+
7549
+ //#endregion
7550
+ //#region ../blocks/src/system/manifest/fieldValidation/writeCanonicalization.ts
7551
+ function allGroupChildPlans(plan) {
7552
+ return plan.childSelection.kind === "all" ? plan.childSelection.fields : plan.childSelection.tabs.flatMap((tab) => tab.fields);
7553
+ }
7554
+ function selectedGroupChildPlans(plan, value) {
7555
+ if (plan.childSelection.kind === "all") return plan.childSelection.fields;
7556
+ if (!isPlainObject$7(value)) return [];
7557
+ const selectedType = value[plan.childSelection.activeTabField];
7558
+ return plan.childSelection.tabs.find((tab) => tab.typeId === selectedType)?.fields ?? [];
7559
+ }
7560
+ function canonicalizeObjectForWrite(plans, value) {
7561
+ if (!isPlainObject$7(value)) return value;
7562
+ const canonical$1 = { ...value };
7563
+ for (const plan of plans) {
7564
+ if (!(plan.fieldId in canonical$1)) continue;
7565
+ canonical$1[plan.fieldId] = canonicalizeFieldValue(plan, canonical$1[plan.fieldId]);
7566
+ }
7567
+ return omitUndefined(canonical$1);
7568
+ }
7569
+ function canonicalizeFieldValue(plan, value) {
7570
+ switch (plan.kind) {
7571
+ case "media": return canonicalizeMedia(value);
7572
+ case "group": return plan.childSelection.kind === "discriminatedTabs" ? canonicalizeDiscriminatedGroup(plan, value) : canonicalizeObjectForWrite(plan.childSelection.fields, value);
7573
+ case "repeater": return canonicalizeRepeater(plan, value);
7574
+ case "string":
7575
+ case "number":
7576
+ case "boolean":
7577
+ case "richText":
7578
+ case "inlineText":
7579
+ case "link":
7580
+ case "select":
7581
+ case "passthrough": return value;
7582
+ }
7583
+ }
7584
+ function canonicalizeMedia(value) {
7585
+ if (!isPlainObject$7(value)) return value;
7586
+ const canonical$1 = { ...value };
7587
+ if (mediaIdentifierSchema.safeParse(canonical$1.identifier).success) {
7588
+ deleteEnvSpecificKeys(canonical$1);
7589
+ }
7590
+ return canonical$1;
7591
+ }
7592
+ function canonicalizeRepeater(plan, value) {
7593
+ if (!isUnknownArray$3(value)) return value;
7594
+ return value.map((item) => {
7595
+ if (!isPlainObject$7(item)) return item;
7596
+ const childPlans = plan.repeatedItemVariants ? plan.repeatedItemVariants.find((variant) => variant.typeId === item._type)?.fields : plan.repeatedItemPlan;
7597
+ return childPlans ? canonicalizeObjectForWrite(childPlans, item) : item;
7598
+ });
7599
+ }
7600
+ function canonicalizeDiscriminatedGroup(plan, value) {
7601
+ if (!isPlainObject$7(value) || plan.childSelection.kind !== "discriminatedTabs") {
7602
+ return value;
7603
+ }
7604
+ const selection = plan.childSelection;
7605
+ const selectedPlans = selectedGroupChildPlans(plan, value);
7606
+ const selectedIds = rootFieldIds(selectedPlans);
7607
+ const allChildIds = rootFieldIds(allGroupChildPlans(plan));
7608
+ const projected = Object.fromEntries(Object.entries(value).filter(([key]) => key === selection.activeTabField || selectedIds.has(key) || !allChildIds.has(key)));
7609
+ const canonical$1 = omitUndefined(canonicalizeObjectForWrite(selectedPlans, projected));
7610
+ if (plan.valueContract !== "boxBackground") return canonical$1;
7611
+ const parsed = boxBackgroundSchema.safeParse(canonical$1);
7612
+ return parsed.success ? parsed.data : canonical$1;
7613
+ }
7614
+ function rootFieldIds(plans) {
7615
+ return new Set(plans.map((child) => child.fieldId.split(".")[0] ?? child.fieldId));
7616
+ }
7617
+ function omitUndefined(value) {
7618
+ if (isUnknownArray$3(value)) return value.map(omitUndefined);
7619
+ if (!isPlainObject$7(value)) return value;
7620
+ return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== undefined).map(([key, field]) => [key, omitUndefined(field)]));
7621
+ }
7622
+ function isPlainObject$7(value) {
7623
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7624
+ }
7625
+ function isUnknownArray$3(value) {
7626
+ return Array.isArray(value);
7627
+ }
7628
+
7629
+ //#endregion
7630
+ //#region ../blocks/src/system/manifest/fieldValidation/layoutGrid.ts
7631
+ function layoutGridRecord(value) {
7632
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
7633
+ }
7634
+ function resolveLayoutGridItemPlans(plan, item) {
7635
+ return plan.repeatedItemVariants?.find((variant) => variant.typeId === item._type)?.fields ?? plan.repeatedItemPlan ?? [];
7636
+ }
7637
+ function layoutGridCoordinateFieldIds(plan) {
7638
+ const grid$5 = plan.layoutGrid;
7639
+ return grid$5 ? [
7640
+ grid$5.spanField,
7641
+ grid$5.rowField,
7642
+ grid$5.columnField
7643
+ ] : [];
7644
+ }
7645
+ function acceptedSpans(plans, spanField) {
7646
+ const span = plans.find((field) => field.fieldId === spanField);
7647
+ if (span?.kind !== "select") return undefined;
7648
+ const constraint = span.constraints.find((entry) => entry.kind === "selectOption");
7649
+ return constraint?.kind === "selectOption" && constraint.source.kind === "static" ? constraint.source.values.map(Number) : [];
7650
+ }
7651
+ function gridIssue(plan, path$3, message) {
7652
+ return {
7653
+ kind: "invalidLayoutGrid",
7654
+ fieldId: plan.fieldId,
7655
+ label: plan.label,
7656
+ path: path$3,
7657
+ message
7658
+ };
7659
+ }
7660
+ function buildLayoutGridForPlan(plan, value, values, ids) {
7661
+ const grid$5 = plan.layoutGrid;
7662
+ if (!grid$5) return null;
7663
+ const columns = grid$5.columns ?? (grid$5.columnsFields ? resolveRepeaterGridColumnsForBreakpoint(values, grid$5.columnsFields, "lg") : 1);
7664
+ const items = value.flatMap((item, index) => {
7665
+ const row$1 = layoutGridRecord(item);
7666
+ return row$1 ? [{
7667
+ id: ids?.[index] ?? String(index),
7668
+ startNewRow: grid$5.startNewRowField ? row$1[grid$5.startNewRowField] === true : false,
7669
+ span: parseRepeaterGridInteger(row$1[grid$5.spanField]) ?? NaN,
7670
+ row: parseRepeaterGridInteger(row$1[grid$5.rowField]) ?? NaN,
7671
+ colStart: parseRepeaterGridInteger(row$1[grid$5.columnField]) ?? NaN,
7672
+ acceptedSpans: acceptedSpans(resolveLayoutGridItemPlans(plan, row$1), grid$5.spanField)
7673
+ }] : [];
7674
+ });
7675
+ return buildRepeaterGridLayout({
7676
+ items,
7677
+ columns,
7678
+ maxRows: grid$5.maxRows,
7679
+ placementPolicy: grid$5.columns === undefined ? "free" : "flow"
7680
+ });
7681
+ }
7682
+ function validateGrid(plan, value, values, path$3) {
7683
+ const layout = buildLayoutGridForPlan(plan, value, values);
7684
+ if (!layout) return [];
7685
+ if (layout.hasCollisions || layout.overCapacity) return [gridIssue(plan, path$3, "Some items overlap or do not fit in this layout. Adjust their widths or positions before saving.")];
7686
+ return layout.hasInvalidItems ? [gridIssue(plan, path$3, "Choose an available width and a valid position for each item before saving.")] : [];
7687
+ }
7688
+ /** Root values resolve the same column bindings as the editor; recurse without changing any content. */
7689
+ function validateLayoutGridPlans(plans, value, rootValues = layoutGridRecord(value) ?? {}, parent = []) {
7690
+ const values = layoutGridRecord(value);
7691
+ if (!values) return [];
7692
+ return plans.flatMap((plan) => {
7693
+ const fieldValue = values[plan.fieldId];
7694
+ const path$3 = [...parent, plan.fieldId];
7695
+ if (plan.kind === "group") return validateLayoutGridPlans(allGroupChildPlans(plan), fieldValue, rootValues, path$3);
7696
+ if (plan.kind !== "repeater" || !Array.isArray(fieldValue)) return [];
7697
+ return [...validateGrid(plan, fieldValue, rootValues, path$3), ...fieldValue.flatMap((item, index) => {
7698
+ const row$1 = layoutGridRecord(item);
7699
+ return row$1 ? validateLayoutGridPlans(resolveLayoutGridItemPlans(plan, row$1), row$1, rootValues, [...path$3, index]) : [];
7700
+ })];
7701
+ });
7702
+ }
7703
+ /** User-authored column edits only; never run this as a load/render projection. */
7704
+ function planLayoutGridColumnEdit(plan, items, before, after) {
7705
+ const grid$5 = plan.layoutGrid;
7706
+ if (!grid$5?.columnsFields) return { kind: "unchanged" };
7707
+ const columns = resolveRepeaterGridColumnsForBreakpoint(after, grid$5.columnsFields, "lg");
7708
+ const previous = buildLayoutGridForPlan(plan, items, before);
7709
+ return previous ? planRepeaterGridColumnChange(previous, columns) : { kind: "unchanged" };
7710
+ }
7711
+
7147
7712
  //#endregion
7148
7713
  //#region ../blocks/src/system/inlineText.ts
7149
7714
  const inlineTextAccentTones = [
@@ -8602,112 +9167,6 @@ function isGeneratedBlockIconStale(value, currentStyleGuideVersionId) {
8602
9167
  return getGeneratedBlockIconStyleState(value, currentStyleGuideVersionId).kind === "stale";
8603
9168
  }
8604
9169
 
8605
- //#endregion
8606
- //#region ../blocks/src/lib/media.ts
8607
- const MEDIA_TYPE_DISCRIMINANTS = mediaTypeDiscriminants;
8608
- const PUBLIC_DOWNLOAD_MEDIA_TYPES = [
8609
- "document",
8610
- "spreadsheet",
8611
- "archive",
8612
- "audio"
8613
- ];
8614
- function isMediaTypeDiscriminant(value) {
8615
- return typeof value === "string" && MEDIA_TYPE_DISCRIMINANTS.includes(value);
8616
- }
8617
- function mediaTypeDiscriminantFromMimeType(mimeType) {
8618
- const classified = classifyKnownMimeType(mimeType);
8619
- if (!classified.ok) return null;
8620
- switch (classified.value.tag) {
8621
- case "image":
8622
- case "video":
8623
- case "audio":
8624
- case "document":
8625
- case "spreadsheet":
8626
- case "archive": return classified.value.tag;
8627
- case "font": return null;
8628
- }
8629
- }
8630
-
8631
- //#endregion
8632
- //#region ../blocks/src/system/manifest/fieldValidation/writeCanonicalization.ts
8633
- function allGroupChildPlans(plan) {
8634
- return plan.childSelection.kind === "all" ? plan.childSelection.fields : plan.childSelection.tabs.flatMap((tab) => tab.fields);
8635
- }
8636
- function selectedGroupChildPlans(plan, value) {
8637
- if (plan.childSelection.kind === "all") return plan.childSelection.fields;
8638
- if (!isPlainObject$7(value)) return [];
8639
- const selectedType = value[plan.childSelection.activeTabField];
8640
- return plan.childSelection.tabs.find((tab) => tab.typeId === selectedType)?.fields ?? [];
8641
- }
8642
- function canonicalizeObjectForWrite(plans, value) {
8643
- if (!isPlainObject$7(value)) return value;
8644
- const canonical$1 = { ...value };
8645
- for (const plan of plans) {
8646
- if (!(plan.fieldId in canonical$1)) continue;
8647
- canonical$1[plan.fieldId] = canonicalizeFieldValue(plan, canonical$1[plan.fieldId]);
8648
- }
8649
- return omitUndefined(canonical$1);
8650
- }
8651
- function canonicalizeFieldValue(plan, value) {
8652
- switch (plan.kind) {
8653
- case "media": return canonicalizeMedia(value);
8654
- case "group": return plan.childSelection.kind === "discriminatedTabs" ? canonicalizeDiscriminatedGroup(plan, value) : canonicalizeObjectForWrite(plan.childSelection.fields, value);
8655
- case "repeater": return canonicalizeRepeater(plan, value);
8656
- case "string":
8657
- case "number":
8658
- case "boolean":
8659
- case "richText":
8660
- case "inlineText":
8661
- case "link":
8662
- case "select":
8663
- case "passthrough": return value;
8664
- }
8665
- }
8666
- function canonicalizeMedia(value) {
8667
- if (!isPlainObject$7(value)) return value;
8668
- const canonical$1 = { ...value };
8669
- if (mediaIdentifierSchema.safeParse(canonical$1.identifier).success) {
8670
- deleteEnvSpecificKeys(canonical$1);
8671
- }
8672
- return canonical$1;
8673
- }
8674
- function canonicalizeRepeater(plan, value) {
8675
- if (!isUnknownArray$3(value)) return value;
8676
- return value.map((item) => {
8677
- if (!isPlainObject$7(item)) return item;
8678
- const childPlans = plan.repeatedItemVariants ? plan.repeatedItemVariants.find((variant) => variant.typeId === item._type)?.fields : plan.repeatedItemPlan;
8679
- return childPlans ? canonicalizeObjectForWrite(childPlans, item) : item;
8680
- });
8681
- }
8682
- function canonicalizeDiscriminatedGroup(plan, value) {
8683
- if (!isPlainObject$7(value) || plan.childSelection.kind !== "discriminatedTabs") {
8684
- return value;
8685
- }
8686
- const selection = plan.childSelection;
8687
- const selectedPlans = selectedGroupChildPlans(plan, value);
8688
- const selectedIds = rootFieldIds(selectedPlans);
8689
- const allChildIds = rootFieldIds(allGroupChildPlans(plan));
8690
- const projected = Object.fromEntries(Object.entries(value).filter(([key]) => key === selection.activeTabField || selectedIds.has(key) || !allChildIds.has(key)));
8691
- const canonical$1 = omitUndefined(canonicalizeObjectForWrite(selectedPlans, projected));
8692
- if (plan.valueContract !== "boxBackground") return canonical$1;
8693
- const parsed = boxBackgroundSchema.safeParse(canonical$1);
8694
- return parsed.success ? parsed.data : canonical$1;
8695
- }
8696
- function rootFieldIds(plans) {
8697
- return new Set(plans.map((child) => child.fieldId.split(".")[0] ?? child.fieldId));
8698
- }
8699
- function omitUndefined(value) {
8700
- if (isUnknownArray$3(value)) return value.map(omitUndefined);
8701
- if (!isPlainObject$7(value)) return value;
8702
- return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== undefined).map(([key, field]) => [key, omitUndefined(field)]));
8703
- }
8704
- function isPlainObject$7(value) {
8705
- return typeof value === "object" && value !== null && !Array.isArray(value);
8706
- }
8707
- function isUnknownArray$3(value) {
8708
- return Array.isArray(value);
8709
- }
8710
-
8711
9170
  //#endregion
8712
9171
  //#region ../blocks/src/system/fields/backgroundContentAudit.ts
8713
9172
  const BACKGROUND_SLOTS = [
@@ -8773,7 +9232,7 @@ function validationContext(options = {}) {
8773
9232
  const isDraft = options.mode === "draft";
8774
9233
  return {
8775
9234
  allowNull: options.allowNull ?? true,
8776
- allowIncomplete: isDraft || (options.allowIncomplete ?? false),
9235
+ allowIncomplete: options.mode === "storable" ? false : isDraft || (options.allowIncomplete ?? false),
8777
9236
  mode: options.mode ?? "strict"
8778
9237
  };
8779
9238
  }
@@ -8797,18 +9256,20 @@ function parseFieldPathKey(key) {
8797
9256
  return Number.isInteger(numeric) && String(numeric) === segment ? numeric : segment;
8798
9257
  });
8799
9258
  }
9259
+ const simpleIssueMessages = {
9260
+ required: "This field is required",
9261
+ invalidEmail: "Enter a valid email address",
9262
+ invalidPhone: "Enter a valid phone number",
9263
+ invalidPattern: "Check the format and try again",
9264
+ invalidUrl: "Enter a valid web address",
9265
+ invalidDate: "Choose a valid date",
9266
+ invalidTime: "Choose a valid time",
9267
+ invalidDateTime: "Choose a valid date and time"
9268
+ };
8800
9269
  function fieldIssueToMessage(issue$1) {
8801
9270
  switch (issue$1.kind) {
8802
- case "required": return "This field is required";
8803
9271
  case "wrongType": return `${issue$1.label} needs ${issue$1.expected}`;
8804
9272
  case "tooLong": return `Use ${issue$1.maximum} characters or fewer`;
8805
- case "invalidEmail": return "Enter a valid email address";
8806
- case "invalidPhone": return "Enter a valid phone number";
8807
- case "invalidPattern": return "Check the format and try again";
8808
- case "invalidUrl": return "Enter a valid web address";
8809
- case "invalidDate": return "Choose a valid date";
8810
- case "invalidTime": return "Choose a valid time";
8811
- case "invalidDateTime": return "Choose a valid date and time";
8812
9273
  case "invalidNumber": return `${issue$1.label} needs a number`;
8813
9274
  case "tooSmall": return `${issue$1.label} must be ${issue$1.minimum} or more`;
8814
9275
  case "tooLarge": return `${issue$1.label} must be ${issue$1.maximum} or less`;
@@ -8821,7 +9282,16 @@ function fieldIssueToMessage(issue$1) {
8821
9282
  case "invalidMedia": return `${issue$1.label} must be a media object with an identifier`;
8822
9283
  case "invalidBoxBackground": return `${issue$1.label} must use a valid background type and matching fields`;
8823
9284
  case "invalidLink": return `${issue$1.label} must be a valid link`;
9285
+ case "invalidLayoutGrid": return issue$1.message;
8824
9286
  case "invalidReference": return `${issue$1.label} must reference a valid item`;
9287
+ case "required":
9288
+ case "invalidDate":
9289
+ case "invalidDateTime":
9290
+ case "invalidEmail":
9291
+ case "invalidPattern":
9292
+ case "invalidPhone":
9293
+ case "invalidTime":
9294
+ case "invalidUrl": return simpleIssueMessages[issue$1.kind];
8825
9295
  default: return assertNever$12(issue$1);
8826
9296
  }
8827
9297
  }
@@ -8936,11 +9406,7 @@ function deriveFieldValidationPlan(field, path$3 = [field.id]) {
8936
9406
  }
8937
9407
  case "link": return makePlan("link", "object", field, path$3, required, commonConstraints(required, { kind: "linkObject" }));
8938
9408
  case "boolean": return makePlan("boolean", "boolean", field, path$3, required, requiredConstraints(required));
8939
- case "repeater": return {
8940
- ...makePlan("repeater", "array", field, path$3, required, commonConstraints(required, { kind: "arrayType" })),
8941
- ...field.polymorphic && field.itemTypes ? { repeatedItemVariants: deriveRepeatedItemVariants(field, [...path$3, 0]) } : { repeatedItemPlan: deriveNestedFieldPlans(getRepeaterFields(field), [...path$3, 0]) },
8942
- constraints: withRepeaterItemConstraints(field, commonConstraints(required, { kind: "arrayType" }))
8943
- };
9409
+ case "repeater": return deriveRepeaterFieldValidationPlan(field, path$3);
8944
9410
  case "group":
8945
9411
  case "modal": return {
8946
9412
  ...makePlan("group", "object", field, path$3, required, requiredConstraints(required)),
@@ -8957,6 +9423,27 @@ function deriveFieldValidationPlan(field, path$3 = [field.id]) {
8957
9423
  default: return assertNever$12(field);
8958
9424
  }
8959
9425
  }
9426
+ function deriveRepeaterFieldValidationPlan(field, path$3) {
9427
+ const required = Boolean(field.required);
9428
+ const constraints = commonConstraints(required, { kind: "arrayType" });
9429
+ return {
9430
+ ...makePlan("repeater", "array", field, path$3, required, constraints),
9431
+ ...field.ui?.repeaterPreset === "layoutGrid" ? { layoutGrid: {
9432
+ startNewRowField: field.ui.layoutGridStartNewRowField,
9433
+ columns: field.ui.layoutGridColumns,
9434
+ columnsFields: field.ui.layoutGridColumnsFields,
9435
+ spanField: resolveRepeaterGridSpanFieldId({ id: field.ui.layoutGridSpanField }),
9436
+ rowField: resolveRepeaterGridRowFieldId({ id: field.ui.layoutGridRowField }),
9437
+ columnField: resolveRepeaterGridColumnFieldId({ id: field.ui.layoutGridColumnField }),
9438
+ maxRows: field.ui.layoutGridMaxRows
9439
+ } } : {},
9440
+ ...field.polymorphic && field.itemTypes ? { repeatedItemVariants: deriveRepeatedItemVariants(field, [...path$3, 0]) } : {
9441
+ repeatedItemPlan: deriveNestedFieldPlans(getRepeaterFields(field), [...path$3, 0]),
9442
+ flattenedGroupIds: getFlattenedGroupIds(field.schema?.fields ?? [])
9443
+ },
9444
+ constraints: withRepeaterItemConstraints(field, constraints)
9445
+ };
9446
+ }
8960
9447
  function deriveFieldValidationPlans(fields$1, parentPath = []) {
8961
9448
  return fields$1.map((field) => deriveFieldValidationPlan(field, [...parentPath, field.id]));
8962
9449
  }
@@ -9006,7 +9493,8 @@ function withRepeaterItemConstraints(field, constraints) {
9006
9493
  function deriveRepeatedItemVariants(field, parentPath) {
9007
9494
  return Object.entries(field.itemTypes ?? {}).map(([typeId, itemType]) => ({
9008
9495
  typeId,
9009
- fields: deriveNestedFieldPlans(getFlattenedRepeaterFields(itemType.fields), parentPath)
9496
+ fields: deriveNestedFieldPlans(getFlattenedRepeaterFields(itemType.fields), parentPath),
9497
+ flattenedGroupIds: getFlattenedGroupIds(itemType.fields)
9010
9498
  }));
9011
9499
  }
9012
9500
  function deriveNestedFieldPlans(fields$1, parentPath) {
@@ -9016,6 +9504,11 @@ function getRepeaterFields(field) {
9016
9504
  const fields$1 = field.polymorphic && field.itemTypes ? Object.values(field.itemTypes).flatMap((itemType) => itemType.fields) : field.schema?.fields ?? [];
9017
9505
  return getFlattenedRepeaterFields(fields$1);
9018
9506
  }
9507
+ /** Only group ids absent from the actual data fields are forbidden containers. */
9508
+ function getFlattenedGroupIds(fields$1) {
9509
+ const dataIds = new Set(getFlattenedRepeaterFields(fields$1).map((field) => field.id));
9510
+ return fields$1.flatMap((field) => field.type === "group" && field.ui?.flattenInRepeater && !dataIds.has(field.id) ? [field.id] : []);
9511
+ }
9019
9512
  function getFlattenedRepeaterFields(fields$1) {
9020
9513
  return fields$1.flatMap((child) => {
9021
9514
  if (child.type === "group" && child.ui?.flattenInRepeater) {
@@ -9096,6 +9589,32 @@ function fieldPlanToNativeInputProps(plan) {
9096
9589
  function normalizeFieldInput(plan, value, options = {}) {
9097
9590
  return normalizeFieldValue(plan, value, validationContext(options));
9098
9591
  }
9592
+ /** Drop obsolete presentation containers only when reading persisted content.
9593
+ * Flat data remains authoritative; unknown variants and legitimate groups survive.
9594
+ */
9595
+ function normalizeLegacyStoredGroupContainers(plans, content) {
9596
+ if (!isPlainObject$6(content)) return content;
9597
+ const planMap = new Map(plans.map((plan) => [plan.fieldId, plan]));
9598
+ const entries = Object.entries(content).map(([key, value]) => {
9599
+ const plan = planMap.get(key);
9600
+ return [key, plan ? normalizeLegacyStoredField(plan, value) : value];
9601
+ });
9602
+ return entries.every(([key, value]) => value === content[key]) ? content : Object.fromEntries(entries);
9603
+ }
9604
+ function normalizeLegacyStoredField(plan, value) {
9605
+ if (plan.kind === "group") {
9606
+ return normalizeLegacyStoredGroupContainers(selectedGroupChildPlans(plan, value), value);
9607
+ }
9608
+ if (plan.kind !== "repeater" || !isUnknownArray$2(value)) return value;
9609
+ const items = value.map((item) => {
9610
+ if (!isPlainObject$6(item)) return item;
9611
+ const selection = selectRepeaterItemPlan(plan, item);
9612
+ if (selection.kind !== "fields") return item;
9613
+ const retained = selection.flattenedGroupIds.some((key) => Object.prototype.hasOwnProperty.call(item, key)) ? Object.fromEntries(Object.entries(item).filter(([key]) => !selection.flattenedGroupIds.includes(key))) : item;
9614
+ return normalizeLegacyStoredGroupContainers(selection.fields, retained);
9615
+ });
9616
+ return items.every((item, index) => item === value[index]) ? value : items;
9617
+ }
9099
9618
  function normalizeStoredContentValues(plans, content, options = {}) {
9100
9619
  if (!content || typeof content !== "object" || Array.isArray(content)) {
9101
9620
  return {};
@@ -9158,7 +9677,8 @@ function normalizeStoredRepeaterItems(plan, value, ctx) {
9158
9677
  return item;
9159
9678
  }
9160
9679
  const itemRecord = item;
9161
- const fields$1 = plan.repeatedItemVariants ? plan.repeatedItemVariants.find((variant) => variant.typeId === itemRecord._type)?.fields : plan.repeatedItemPlan;
9680
+ const selection = selectRepeaterItemPlan(plan, itemRecord);
9681
+ const fields$1 = selection.kind === "fields" ? selection.fields : undefined;
9162
9682
  if (!fields$1) {
9163
9683
  return item;
9164
9684
  }
@@ -9215,7 +9735,8 @@ function normalizeRepeaterItems(plan, value, ctx) {
9215
9735
  return value.map((item) => {
9216
9736
  if (!item || typeof item !== "object" || Array.isArray(item)) return item;
9217
9737
  const itemRecord = item;
9218
- const fields$1 = plan.repeatedItemVariants ? plan.repeatedItemVariants.find((variant) => variant.typeId === itemRecord._type)?.fields : plan.repeatedItemPlan;
9738
+ const selection = selectRepeaterItemPlan(plan, itemRecord);
9739
+ const fields$1 = selection.kind === "fields" ? selection.fields : undefined;
9219
9740
  if (!fields$1) return item;
9220
9741
  return normalizeObjectChildren(fields$1, itemRecord, ctx);
9221
9742
  });
@@ -9230,13 +9751,13 @@ function acceptsOptionalBlankAsMissing(plan, value) {
9230
9751
  return plan.constraints.some((constraint) => constraint.kind === "urlFormat" || constraint.kind === "slugFormat" || constraint.kind === "emailFormat" || constraint.kind === "phoneFormat" || constraint.kind === "patternFormat");
9231
9752
  }
9232
9753
  function validateNormalizedFieldValue(plan, value, ctx) {
9233
- if (value === null && (ctx.allowNull || acceptsOptionalNullValue(plan))) return [];
9234
- if (isMissingForPlan(plan, value)) {
9754
+ if (value === null && acceptsNullValue(plan, ctx)) return [];
9755
+ if (isMissingForPlan(plan, value, ctx.mode)) {
9235
9756
  return shouldRequireValue(plan, value, ctx) ? [requiredIssue(plan, value)] : [];
9236
9757
  }
9237
9758
  switch (plan.kind) {
9238
9759
  case "string": return validateStringPlan(plan, value, ctx);
9239
- case "number": return validateNumberPlan(plan, value);
9760
+ case "number": return validateNumberPlan(plan, value, ctx);
9240
9761
  case "boolean": return typeof value === "boolean" ? [] : [issue(plan, "wrongType", { expected: "a true/false value" })];
9241
9762
  case "select": return validateSelectPlan(plan, value);
9242
9763
  case "richText": return isValidRichTextDoc(value) ? [] : [issue(plan, "invalidRichText")];
@@ -9260,14 +9781,18 @@ function validateMediaPlan(plan, value, ctx) {
9260
9781
  }
9261
9782
  function shouldRequireValue(plan, value, ctx) {
9262
9783
  if (!plan.required || ctx.allowIncomplete) return false;
9263
- if (value === null && ctx.allowNull) return false;
9784
+ if (value === null && ctx.allowNull && ctx.mode !== "storable") return false;
9264
9785
  return true;
9265
9786
  }
9787
+ function acceptsNullValue(plan, ctx) {
9788
+ return ctx.mode === "storable" ? !plan.required : ctx.allowNull || acceptsOptionalNullValue(plan);
9789
+ }
9266
9790
  function acceptsOptionalNullValue(plan) {
9267
9791
  return !plan.required && (plan.kind === "media" || plan.kind === "number");
9268
9792
  }
9269
- function isMissingForPlan(plan, value) {
9793
+ function isMissingForPlan(plan, value, mode) {
9270
9794
  if (value === undefined) return true;
9795
+ if (mode === "storable") return value === null || value === "";
9271
9796
  if (value === null) return plan.required;
9272
9797
  if (typeof value === "string" && value.trim() === "") return true;
9273
9798
  if (Array.isArray(value) && value.length === 0) return true;
@@ -9286,7 +9811,7 @@ function validateStringConstraint(plan, constraint, value, ctx) {
9286
9811
  return isValidIconPickerValue(value) ? [] : [issue(plan, "wrongType", { expected: "text or icon" })];
9287
9812
  }
9288
9813
  if (typeof value !== "string") return [issue(plan, "wrongType", { expected: "text" })];
9289
- if (constraint.kind === "textType" || constraint.kind === "referenceText") return [];
9814
+ if (constraint.kind === "textType" || constraint.kind === "referenceText" || ctx.mode === "storable") return [];
9290
9815
  return constraint.kind === "maxLength" ? validateAuthoredTextLength(plan, value, constraint.maximum, ctx) : validateStringFormat(plan, constraint, value);
9291
9816
  }
9292
9817
  const SIMPLE_STRING_FORMATS = {
@@ -9328,7 +9853,7 @@ function validateStringFormat(plan, constraint, value) {
9328
9853
  }
9329
9854
  /** Authoring limits govern stored block copy; resolved entry text has its own owner. */
9330
9855
  function validateAuthoredTextLength(plan, text$1, maximum, ctx) {
9331
- return ctx.mode !== "render" && maximum !== undefined && text$1.length > maximum ? [issue(plan, "tooLong", { maximum })] : [];
9856
+ return ctx.mode !== "render" && ctx.mode !== "storable" && maximum !== undefined && text$1.length > maximum ? [issue(plan, "tooLong", { maximum })] : [];
9332
9857
  }
9333
9858
  function isValidIconPickerValue(value) {
9334
9859
  if (typeof value === "string") return true;
@@ -9336,8 +9861,9 @@ function isValidIconPickerValue(value) {
9336
9861
  if (value.kind === "none") return true;
9337
9862
  return parseBlockIconValue(value).kind !== "none";
9338
9863
  }
9339
- function validateNumberPlan(plan, value) {
9864
+ function validateNumberPlan(plan, value, ctx) {
9340
9865
  if (!isValidNumber(value)) return [issue(plan, "invalidNumber")];
9866
+ if (ctx.mode === "storable") return [];
9341
9867
  return plan.constraints.flatMap((constraint) => validateNumberConstraint(plan, constraint, Number(value)));
9342
9868
  }
9343
9869
  function validateNumberConstraint(plan, constraint, value) {
@@ -9356,7 +9882,7 @@ function validateSelectPlan(plan, value) {
9356
9882
  }
9357
9883
  function validateRepeaterPlan(plan, value, ctx) {
9358
9884
  if (!Array.isArray(value)) return [issue(plan, "wrongType", { expected: "a list" })];
9359
- const constraintIssues = plan.constraints.flatMap((constraint) => {
9885
+ const constraintIssues = ctx.mode === "storable" ? [] : plan.constraints.flatMap((constraint) => {
9360
9886
  switch (constraint.kind) {
9361
9887
  case "required":
9362
9888
  case "arrayType": return [];
@@ -9373,6 +9899,22 @@ function validateRepeaterPlan(plan, value, ctx) {
9373
9899
  });
9374
9900
  return [...constraintIssues, ...value.flatMap((item, index) => validateRepeaterItem(plan, item, index, ctx))];
9375
9901
  }
9902
+ /** Selects both the value fields and forbidden presentation containers together. */
9903
+ function selectRepeaterItemPlan(plan, item) {
9904
+ if (plan.repeatedItemVariants) {
9905
+ const variant = plan.repeatedItemVariants.find((candidate) => candidate.typeId === item._type);
9906
+ return variant ? {
9907
+ kind: "fields",
9908
+ fields: variant.fields,
9909
+ flattenedGroupIds: variant.flattenedGroupIds ?? []
9910
+ } : { kind: "invalidType" };
9911
+ }
9912
+ return plan.repeatedItemPlan ? {
9913
+ kind: "fields",
9914
+ fields: plan.repeatedItemPlan,
9915
+ flattenedGroupIds: plan.flattenedGroupIds ?? []
9916
+ } : { kind: "untyped" };
9917
+ }
9376
9918
  function validateRepeaterItem(plan, item, index, ctx) {
9377
9919
  if (!isPlainObject$6(item)) {
9378
9920
  return [{
@@ -9383,9 +9925,8 @@ function validateRepeaterItem(plan, item, index, ctx) {
9383
9925
  expected: "an object"
9384
9926
  }];
9385
9927
  }
9386
- const itemType = item._type;
9387
- const variant = plan.repeatedItemVariants ? plan.repeatedItemVariants.find((candidate) => candidate.typeId === itemType) : undefined;
9388
- if (plan.repeatedItemVariants && !variant) {
9928
+ const selection = selectRepeaterItemPlan(plan, item);
9929
+ if (selection.kind === "invalidType") {
9389
9930
  return [{
9390
9931
  kind: "invalidOption",
9391
9932
  path: [
@@ -9397,12 +9938,27 @@ function validateRepeaterItem(plan, item, index, ctx) {
9397
9938
  label: `${plan.label} item type`
9398
9939
  }];
9399
9940
  }
9400
- const fields$1 = variant?.fields ?? plan.repeatedItemPlan;
9401
- if (!fields$1) return [];
9402
- return fields$1.flatMap((childPlan) => {
9941
+ if (selection.kind === "untyped") return [];
9942
+ const containerIssues = validateFlattenedGroupContainers(selection.flattenedGroupIds, item, plan, index, ctx);
9943
+ return [...containerIssues, ...selection.fields.flatMap((childPlan) => {
9944
+ if (ctx.layoutGridCoordinates === "projected" && layoutGridCoordinateFieldIds(plan).includes(childPlan.fieldId)) return [];
9403
9945
  const indexedPlan = materializeRepeaterItemPlan(childPlan, plan.path, index);
9404
9946
  return validateNormalizedFieldValue(indexedPlan, item[childPlan.fieldId], ctx);
9405
- });
9947
+ })];
9948
+ }
9949
+ function validateFlattenedGroupContainers(groupIds, item, plan, index, ctx) {
9950
+ if (ctx.mode === "render") return [];
9951
+ return groupIds.flatMap((fieldId) => Object.prototype.hasOwnProperty.call(item, fieldId) ? [{
9952
+ kind: "wrongType",
9953
+ path: [
9954
+ ...plan.path,
9955
+ index,
9956
+ fieldId
9957
+ ],
9958
+ fieldId,
9959
+ label: plan.label,
9960
+ expected: "valid settings"
9961
+ }] : []);
9406
9962
  }
9407
9963
  function validateGroupPlan(plan, value, ctx) {
9408
9964
  if (!isPlainObject$6(value)) return [issue(plan, "wrongType", { expected: "an object" })];
@@ -9505,19 +10061,22 @@ function requiredIssue(plan, value) {
9505
10061
  if (Array.isArray(value)) return issue(plan, "required", { emptyOrigin: "array" });
9506
10062
  return issue(plan, "required");
9507
10063
  }
9508
- function fieldPlanToZod(plan, options = {}) {
9509
- const ctx = validationContext(options);
10064
+ function fieldPlanToZod(plan, options = {}, layoutGridCoordinates = "validate") {
10065
+ const ctx = {
10066
+ ...validationContext(options),
10067
+ layoutGridCoordinates
10068
+ };
9510
10069
  return z.any().transform((value) => normalizeFieldValue(plan, value, ctx)).superRefine((value, zodCtx) => {
9511
- for (const validationIssue of validateNormalizedFieldValue(plan, value, ctx)) {
10070
+ validateNormalizedFieldValue(plan, value, ctx).forEach((validationIssue) => {
9512
10071
  addFieldIssueToZodContext(zodCtx, validationIssue, plan.path);
9513
- }
10072
+ });
9514
10073
  });
9515
10074
  }
9516
10075
  function fieldPresenceForPlan(plan) {
9517
10076
  return plan.required ? { kind: "required" } : { kind: "optional" };
9518
10077
  }
9519
- function fieldPlanToObjectPropertyZod(plan, options = {}) {
9520
- const schema = fieldPlanToZod(plan, options);
10078
+ function fieldPlanToObjectPropertyZod(plan, options = {}, layoutGridCoordinates = "validate") {
10079
+ const schema = fieldPlanToZod(plan, options, layoutGridCoordinates);
9521
10080
  const presence = fieldPresenceForPlan(plan);
9522
10081
  switch (presence.kind) {
9523
10082
  case "required": return schema;
@@ -9526,11 +10085,14 @@ function fieldPlanToObjectPropertyZod(plan, options = {}) {
9526
10085
  }
9527
10086
  }
9528
10087
  /** Validate stored or runtime fields without applying editor/write normalization. */
9529
- function fieldPlanToStrictObjectPropertyZod(plan, mode = "strict") {
9530
- const ctx = strictValidationContext({
9531
- allowNull: false,
9532
- mode
9533
- });
10088
+ function fieldPlanToStrictObjectPropertyZod(plan, mode = "strict", layoutGridCoordinates = "validate") {
10089
+ const ctx = {
10090
+ ...strictValidationContext({
10091
+ allowNull: false,
10092
+ mode
10093
+ }),
10094
+ layoutGridCoordinates
10095
+ };
9534
10096
  const schema = z.any().superRefine((value, zodCtx) => {
9535
10097
  validateNormalizedFieldValue(plan, value, ctx).forEach((validationIssue) => {
9536
10098
  addFieldIssueToZodContext(zodCtx, validationIssue, plan.path);
@@ -9538,76 +10100,35 @@ function fieldPlanToStrictObjectPropertyZod(plan, mode = "strict") {
9538
10100
  });
9539
10101
  return plan.required ? schema : schema.optional();
9540
10102
  }
9541
- function addFieldIssueToZodContext(zodCtx, validationIssue, rootPath) {
9542
- const message = fieldIssueToMessage(validationIssue);
9543
- const path$3 = relativeZodIssuePath(validationIssue.path, rootPath);
9544
- const issuePath = path$3.length > 0 ? { path: toZodIssuePath(path$3) } : {};
9545
- switch (validationIssue.kind) {
9546
- case "required":
9547
- if (validationIssue.emptyOrigin) {
9548
- zodCtx.addIssue({
9549
- ...issuePath,
9550
- code: "too_small",
9551
- origin: validationIssue.emptyOrigin,
9552
- minimum: 1,
9553
- inclusive: true,
9554
- message
9555
- });
9556
- return;
9557
- }
9558
- zodCtx.addIssue({
9559
- ...issuePath,
9560
- code: "invalid_type",
9561
- expected: "nonoptional",
9562
- message
9563
- });
9564
- return;
9565
- case "tooLong":
9566
- zodCtx.addIssue({
9567
- ...issuePath,
9568
- code: "too_big",
9569
- origin: "string",
9570
- maximum: validationIssue.maximum,
9571
- inclusive: true,
9572
- message
9573
- });
9574
- return;
9575
- case "invalidOption":
9576
- zodCtx.addIssue({
9577
- ...issuePath,
9578
- code: "invalid_value",
9579
- values: [],
9580
- message
9581
- });
9582
- return;
9583
- case "wrongType":
9584
- case "invalidEmail":
9585
- case "invalidPhone":
9586
- case "invalidPattern":
9587
- case "invalidUrl":
9588
- case "invalidDate":
9589
- case "invalidTime":
9590
- case "invalidDateTime":
9591
- case "invalidNumber":
9592
- case "tooSmall":
9593
- case "tooLarge":
9594
- case "invalidSlug":
9595
- case "tooFewItems":
9596
- case "tooManyItems":
9597
- case "invalidRichText":
9598
- case "invalidInlineText":
9599
- case "invalidMedia":
9600
- case "invalidBoxBackground":
9601
- case "invalidLink":
9602
- case "invalidReference":
9603
- zodCtx.addIssue({
9604
- ...issuePath,
9605
- code: "custom",
9606
- message
9607
- });
9608
- return;
9609
- default: assertNever$12(validationIssue);
9610
- }
10103
+ function fieldIssueToZodDetail(issue$1) {
10104
+ if (issue$1.kind === "required") return issue$1.emptyOrigin ? {
10105
+ code: "too_small",
10106
+ origin: issue$1.emptyOrigin,
10107
+ minimum: 1,
10108
+ inclusive: true
10109
+ } : {
10110
+ code: "invalid_type",
10111
+ expected: "nonoptional"
10112
+ };
10113
+ if (issue$1.kind === "tooLong") return {
10114
+ code: "too_big",
10115
+ origin: "string",
10116
+ maximum: issue$1.maximum,
10117
+ inclusive: true
10118
+ };
10119
+ if (issue$1.kind === "invalidOption") return {
10120
+ code: "invalid_value",
10121
+ values: []
10122
+ };
10123
+ return { code: "custom" };
10124
+ }
10125
+ function addFieldIssueToZodContext(zodCtx, issue$1, rootPath) {
10126
+ const detail = fieldIssueToZodDetail(issue$1);
10127
+ zodCtx.addIssue({
10128
+ ...detail,
10129
+ path: toZodIssuePath(relativeZodIssuePath(issue$1.path, rootPath)),
10130
+ message: fieldIssueToMessage(issue$1)
10131
+ });
9611
10132
  }
9612
10133
  function relativeZodIssuePath(path$3, rootPath) {
9613
10134
  return startsWithFieldPath(path$3, rootPath) ? path$3.slice(rootPath.length) : path$3;
@@ -9668,7 +10189,7 @@ function sameFieldPath(left, right) {
9668
10189
  function validateManifestValues(fields$1, values, options = {}) {
9669
10190
  const plans = deriveFieldValidationPlans(fields$1);
9670
10191
  const normalized = normalizeStoredContentValues(plans, values, options);
9671
- return plans.flatMap((plan) => validateFieldValue(plan, normalized[plan.fieldId], options));
10192
+ return [...plans.flatMap((plan) => validateFieldValue(plan, normalized[plan.fieldId], options)), ...validateLayoutGridPlans(plans, normalized)];
9672
10193
  }
9673
10194
  function compilePattern(pattern) {
9674
10195
  try {
@@ -9771,6 +10292,63 @@ function isEmptyRichTextDoc(value) {
9771
10292
  return first?.type === "paragraph" && (!Array.isArray(first.content) || first.content.length === 0);
9772
10293
  }
9773
10294
 
10295
+ //#endregion
10296
+ //#region ../blocks/src/system/manifest/fieldValidation/layoutGridRendering.ts
10297
+ function invalidCoordinateIds(plan, items, itemIds) {
10298
+ const ids = layoutGridCoordinateFieldIds(plan);
10299
+ return items.flatMap((item, index) => {
10300
+ const row$1 = layoutGridRecord(item);
10301
+ const invalid$1 = row$1 !== null && resolveLayoutGridItemPlans(plan, row$1).some((field) => ids.includes(field.fieldId) && !fieldPlanToStrictObjectPropertyZod(field).safeParse(row$1[field.fieldId]).success);
10302
+ return invalid$1 ? [itemIds?.[index] ?? String(index)] : [];
10303
+ });
10304
+ }
10305
+ /** Shared raw-coordinate admission for editor and public rendering; retains diagnostics. */
10306
+ function admitLayoutGridForPlan(plan, value, rootValues, ids) {
10307
+ const layout = buildLayoutGridForPlan(plan, value, rootValues, ids);
10308
+ const invalidIds = invalidCoordinateIds(plan, value, ids);
10309
+ return layout && invalidIds.length > 0 ? {
10310
+ ...layout,
10311
+ hasInvalidItems: true,
10312
+ invalidCoordinateIds: invalidIds
10313
+ } : layout;
10314
+ }
10315
+ function projectGrid(plan, value, rootValues) {
10316
+ const grid$5 = plan.layoutGrid;
10317
+ const layout = admitLayoutGridForPlan(plan, value, rootValues);
10318
+ if (!grid$5 || !layout) return value;
10319
+ const authored = layout;
10320
+ const projected = projectRepeaterGridLayoutForRendering(authored);
10321
+ if (projected === authored) return value;
10322
+ return value.map((item, index) => {
10323
+ const row$1 = layoutGridRecord(item);
10324
+ const placement = projected.byId[String(index)];
10325
+ if (!row$1 || !placement) return item;
10326
+ const spanPlan = resolveLayoutGridItemPlans(plan, row$1).find((field) => field.fieldId === grid$5.spanField);
10327
+ return {
10328
+ ...row$1,
10329
+ [grid$5.spanField]: spanPlan?.kind === "number" ? placement.span : String(placement.span),
10330
+ [grid$5.rowField]: placement.row,
10331
+ [grid$5.columnField]: placement.colStart
10332
+ };
10333
+ });
10334
+ }
10335
+ /** Only layout coordinates are projected. Array order, item data and media references remain intact. */
10336
+ function projectLayoutGridPlansForRendering(plans, value, rootValues = layoutGridRecord(value) ?? {}) {
10337
+ const values = layoutGridRecord(value);
10338
+ if (!values) return value;
10339
+ return plans.reduce((projected, plan) => {
10340
+ const fieldValue = projected[plan.fieldId];
10341
+ const next = plan.kind === "group" ? projectLayoutGridPlansForRendering(allGroupChildPlans(plan), fieldValue, rootValues) : plan.kind === "repeater" && Array.isArray(fieldValue) ? projectGrid(plan, fieldValue, rootValues).map((item) => {
10342
+ const row$1 = layoutGridRecord(item);
10343
+ return row$1 ? projectLayoutGridPlansForRendering(resolveLayoutGridItemPlans(plan, row$1), row$1, rootValues) : item;
10344
+ }) : fieldValue;
10345
+ return next === fieldValue ? projected : {
10346
+ ...projected,
10347
+ [plan.fieldId]: next
10348
+ };
10349
+ }, values);
10350
+ }
10351
+
9774
10352
  //#endregion
9775
10353
  //#region ../blocks/src/system/manifest/validation.ts
9776
10354
  const compiledManifestContentContract = Symbol("compiledManifestContentContract");
@@ -9802,6 +10380,7 @@ function compileManifestContentContract(manifestOrFields, options = {}) {
9802
10380
  return Object.freeze({ [compiledManifestContentContract]: Object.freeze({
9803
10381
  plans,
9804
10382
  strictStoredSchema,
10383
+ renderSourceSchema: deriveStrictContentZodFromPlans(plans, "render-source"),
9805
10384
  writeSchema: deriveNormalizedContentZodFromPlans(plans, strictOptions, normalizeManifestContentForWrite).superRefine((value, ctx) => {
9806
10385
  if (options.unknownFields !== "reject") return;
9807
10386
  unknownManifestFieldPaths(plans, value).forEach((path$3) => {
@@ -9817,6 +10396,11 @@ function compileManifestContentContract(manifestOrFields, options = {}) {
9817
10396
  allowIncomplete: true,
9818
10397
  mode: "draft"
9819
10398
  }, normalizeStoredContentValues),
10399
+ renderDraftSchema: deriveNormalizedContentZodFromPlans(plans, {
10400
+ allowNull: true,
10401
+ allowIncomplete: true,
10402
+ mode: "draft"
10403
+ }, normalizeStoredContentValues, false),
9820
10404
  renderSchema: deriveStrictContentZodFromPlans(plans, "render"),
9821
10405
  findNonPortableMediaPaths: (content) => findNonPortableMediaPaths(plans, content)
9822
10406
  }) });
@@ -9855,7 +10439,8 @@ function findNonPortableManifestMediaPaths(contract, content) {
9855
10439
  /** Strictly admits already-persisted content without repairing it. */
9856
10440
  function admitStoredManifestContent(contract, content) {
9857
10441
  const state = contract[compiledManifestContentContract];
9858
- const nonPortablePaths = state.findNonPortableMediaPaths(content);
10442
+ const normalized = normalizeLegacyStoredGroupContainers(state.plans, content);
10443
+ const nonPortablePaths = state.findNonPortableMediaPaths(normalized);
9859
10444
  if (nonPortablePaths.length > 0) {
9860
10445
  return {
9861
10446
  ok: false,
@@ -9865,10 +10450,32 @@ function admitStoredManifestContent(contract, content) {
9865
10450
  }
9866
10451
  };
9867
10452
  }
9868
- const parsed = state.strictStoredSchema.safeParse(content);
10453
+ const parsed = state.strictStoredSchema.safeParse(normalized);
9869
10454
  return parsed.success ? {
9870
10455
  ok: true,
9871
- value: content
10456
+ value: normalized
10457
+ } : {
10458
+ ok: false,
10459
+ error: zodAdmissionError(parsed.error)
10460
+ };
10461
+ }
10462
+ /** Public reads alone may project invalid grid geometry. All other stored-shape and portability checks remain strict. */
10463
+ function admitStoredManifestContentForRendering(contract, content) {
10464
+ const state = contract[compiledManifestContentContract];
10465
+ const normalized = normalizeLegacyStoredGroupContainers(state.plans, content);
10466
+ const paths = state.findNonPortableMediaPaths(normalized);
10467
+ if (paths.length > 0) return {
10468
+ ok: false,
10469
+ error: {
10470
+ kind: "non_portable_media",
10471
+ paths
10472
+ }
10473
+ };
10474
+ const projected = projectLayoutGridPlansForRendering(state.plans, normalized);
10475
+ const parsed = state.renderSourceSchema.safeParse(projected);
10476
+ return parsed.success ? {
10477
+ ok: true,
10478
+ value: projected
9872
10479
  } : {
9873
10480
  ok: false,
9874
10481
  error: zodAdmissionError(parsed.error)
@@ -9916,6 +10523,24 @@ function validateManifestDraft(contract, data) {
9916
10523
  issues: [firstIssue, ...remainingIssues]
9917
10524
  };
9918
10525
  }
10526
+ /** Public template bases may be incomplete until bindings resolve; only grid geometry is projected. */
10527
+ function validateManifestDraftForRendering(contract, data) {
10528
+ const state = contract[compiledManifestContentContract];
10529
+ const projected = projectLayoutGridPlansForRendering(state.plans, data);
10530
+ const parsed = state.renderDraftSchema.safeParse(projected);
10531
+ if (parsed.success) return {
10532
+ kind: "previewable",
10533
+ data: projected,
10534
+ issues: []
10535
+ };
10536
+ const [firstIssue, ...remainingIssues] = zodAdmissionIssues(parsed.error);
10537
+ if (!firstIssue) throw new Error("Render draft validation failed without an issue");
10538
+ return {
10539
+ kind: "invalid",
10540
+ data: projected,
10541
+ issues: [firstIssue, ...remainingIssues]
10542
+ };
10543
+ }
9919
10544
  /**
9920
10545
  * Checks whether a raw editor candidate is schema-ready for the effectful
9921
10546
  * media-resolution/write boundary without granting a stored proof.
@@ -9942,8 +10567,9 @@ function validateManifestWriteCandidate(contract, data) {
9942
10567
  * the resulting render shape before granting the renderable proof.
9943
10568
  */
9944
10569
  function deriveRenderableManifestContent(contract, stored, project) {
9945
- const projected = project(stored);
9946
- const parsed = contract[compiledManifestContentContract].renderSchema.safeParse(projected);
10570
+ const state = contract[compiledManifestContentContract];
10571
+ const projected = projectLayoutGridPlansForRendering(state.plans, normalizeLegacyStoredGroupContainers(state.plans, project(stored)));
10572
+ const parsed = state.renderSchema.safeParse(projected);
9947
10573
  return parsed.success ? {
9948
10574
  ok: true,
9949
10575
  value: projected
@@ -9958,33 +10584,50 @@ function deriveRenderableManifestContent(contract, stored, project) {
9958
10584
  * grants a stored proof, so hydrated media cannot flow back to persistence.
9959
10585
  */
9960
10586
  function admitTransportedRenderableManifestContent(contract, content) {
9961
- const parsed = contract[compiledManifestContentContract].renderSchema.safeParse(content);
10587
+ const state = contract[compiledManifestContentContract];
10588
+ const projected = projectLayoutGridPlansForRendering(state.plans, content);
10589
+ const parsed = state.renderSchema.safeParse(projected);
9962
10590
  return parsed.success ? {
9963
10591
  ok: true,
9964
- value: content
10592
+ value: projected
9965
10593
  } : {
9966
10594
  ok: false,
9967
10595
  error: zodAdmissionError(parsed.error)
9968
10596
  };
9969
10597
  }
9970
- function deriveNormalizedContentZodFromPlans(plans, options, normalize$4) {
10598
+ function deriveNormalizedContentZodFromPlans(plans, options, normalize$4, validateLayoutGrids = true) {
9971
10599
  const isDraft = options.mode === "draft";
9972
- const baseSchema = deriveContentZodFromPlans(plans, options);
10600
+ const baseSchema = deriveContentZodFromPlans(plans, options, validateLayoutGrids);
9973
10601
  if (isDraft) {
9974
10602
  return z.preprocess((content) => preprocessEmptyValues(normalize$4(plans, content, options)), baseSchema);
9975
10603
  }
9976
10604
  return z.preprocess((content) => normalize$4(plans, content, options), baseSchema);
9977
10605
  }
9978
- function deriveContentZodFromPlans(plans, options) {
9979
- const shape = {};
9980
- for (const plan of plans) {
9981
- shape[plan.fieldId] = fieldPlanToObjectPropertyZod(plan, options);
9982
- }
9983
- return z.object(shape).catchall(z.unknown()).passthrough();
10606
+ function deriveContentZodFromPlans(plans, options, validateLayoutGrids = true) {
10607
+ const shape = Object.fromEntries(plans.map((plan) => [plan.fieldId, fieldPlanToObjectPropertyZod(plan, options, validateLayoutGrids ? "validate" : "projected")]));
10608
+ return z.object(shape).catchall(z.unknown()).passthrough().superRefine((values, ctx) => {
10609
+ if (!validateLayoutGrids) return;
10610
+ validateLayoutGridPlans(plans, values).forEach((issue$1) => {
10611
+ ctx.addIssue({
10612
+ code: "custom",
10613
+ path: [...issue$1.path],
10614
+ message: issue$1.message
10615
+ });
10616
+ });
10617
+ });
9984
10618
  }
9985
10619
  function deriveStrictContentZodFromPlans(plans, mode = "strict") {
9986
- const shape = Object.fromEntries(plans.map((plan) => [plan.fieldId, fieldPlanToStrictObjectPropertyZod(plan, mode)]));
9987
- return z.object(shape).catchall(z.unknown()).passthrough();
10620
+ const shape = Object.fromEntries(plans.map((plan) => [plan.fieldId, fieldPlanToStrictObjectPropertyZod(plan, mode === "render" ? "render" : "strict", mode === "strict" ? "validate" : "projected")]));
10621
+ return z.object(shape).catchall(z.unknown()).passthrough().superRefine((values, ctx) => {
10622
+ if (mode !== "strict") return;
10623
+ validateLayoutGridPlans(plans, values).forEach((issue$1) => {
10624
+ ctx.addIssue({
10625
+ code: "custom",
10626
+ path: [...issue$1.path],
10627
+ message: issue$1.message
10628
+ });
10629
+ });
10630
+ });
9988
10631
  }
9989
10632
  function derivePlansFromManifestOrFields(manifestOrFields) {
9990
10633
  const fields$1 = Array.isArray(manifestOrFields) ? manifestOrFields : manifestOrFields.fields ?? [];
@@ -12306,6 +12949,7 @@ function styledSection(config$1) {
12306
12949
  baseClass,
12307
12950
  spacing,
12308
12951
  fullWidthPath,
12952
+ photoPaintsGround: renderBackgroundImage,
12309
12953
  skinSlot: config$1.skinSlot
12310
12954
  }), [renderedBackgroundNode, ...childrenArray]);
12311
12955
  }
@@ -12317,7 +12961,10 @@ function sectionAppearanceProps(input) {
12317
12961
  from: `${input.bindFrom}.background`,
12318
12962
  transforms: [{
12319
12963
  id: "sectionStyles.resolveColorsStyle",
12320
- options: { fallbackToken: input.background }
12964
+ options: {
12965
+ fallbackToken: input.background,
12966
+ photoPaintsGround: input.photoPaintsGround
12967
+ }
12321
12968
  }]
12322
12969
  } },
12323
12970
  className: { $bind: {
@@ -13487,70 +14134,6 @@ function applyAlpha(color, opacity) {
13487
14134
  return color;
13488
14135
  }
13489
14136
 
13490
- //#endregion
13491
- //#region ../blocks/src/system/fields/backgroundContent.ts
13492
- /**
13493
- * Replaces only the background medium while retaining presentation fields that
13494
- * are valid for the resulting image variant. Stale color/gradient tab state is
13495
- * never copied into the new value.
13496
- */
13497
- function replaceBackgroundWithImage(value, image, defaults = {}) {
13498
- const current = parseBackgroundContent(value);
13499
- let candidate;
13500
- if (current?.type === "image") {
13501
- const { type: _type, image: _image, ...presentation } = current;
13502
- candidate = {
13503
- ...defaults,
13504
- ...presentation,
13505
- type: "image",
13506
- image
13507
- };
13508
- } else if (current?.type === "video") {
13509
- const { type: _type, image: _image, showVideoOnMobile: _mobile, ...presentation } = current;
13510
- candidate = {
13511
- ...defaults,
13512
- ...presentation,
13513
- type: "image",
13514
- image
13515
- };
13516
- } else {
13517
- candidate = {
13518
- ...defaults,
13519
- ...commonPresentation(current),
13520
- type: "image",
13521
- image
13522
- };
13523
- }
13524
- const canonical$1 = boxBackgroundSchema.parse(candidate);
13525
- if (canonical$1.type !== "image") {
13526
- throw new Error("Image background construction produced a non-image variant");
13527
- }
13528
- return canonical$1;
13529
- }
13530
- function parseBackgroundContent(value) {
13531
- const result = boxBackgroundSchema.safeParse(value);
13532
- return result.success ? result.data : null;
13533
- }
13534
- function commonPresentation(background) {
13535
- return background ? {
13536
- contentOwner: background.contentOwner,
13537
- textColor: background.textColor,
13538
- headingColor: background.headingColor,
13539
- linkColor: background.linkColor,
13540
- supportingColor: background.supportingColor
13541
- } : {};
13542
- }
13543
- function parseBackgroundOverlay(value) {
13544
- const result = boxBackgroundOverlaySchema.safeParse(value);
13545
- return result.success ? result.data : undefined;
13546
- }
13547
- /** Rendering accepts explicit application sources separately from stored references. */
13548
- const runtimeBackgroundContentSchema = runtimeBoxBackgroundSchema;
13549
- function parseRuntimeBackgroundContent(value) {
13550
- const result = runtimeBackgroundContentSchema.safeParse(value);
13551
- return result.success ? result.data : null;
13552
- }
13553
-
13554
14137
  //#endregion
13555
14138
  //#region ../blocks/src/system/transforms/registry/_utils/themeColorResolution.ts
13556
14139
  function resolveBackgroundColorValue(value, context) {
@@ -13578,7 +14161,7 @@ function resolveSurfaceForegroundStyles(value, context, options) {
13578
14161
  const key = JSON.stringify([
13579
14162
  value,
13580
14163
  context.proseHoverCandidate,
13581
- options?.fallbackToken,
14164
+ options,
13582
14165
  context.data?._lookSlots !== undefined
13583
14166
  ]);
13584
14167
  const cache = sectionSurfaceCache.get(context.theme) ?? new Map();
@@ -13595,9 +14178,13 @@ function resolveSurfaceForegroundStyles(value, context, options) {
13595
14178
  }
13596
14179
  /** Cache only the owner's result; no foreground or button rules live here. */
13597
14180
  function resolveRuntimeSectionSurface(value, context, options) {
14181
+ const background = resolveSectionBackground(parseCanonicalRuntimeBackground({ _sectionStyles: { background: value } }), null, undefined, {
14182
+ paintsGround: options?.photoPaintsGround !== false,
14183
+ fallbackToken: options?.fallbackToken
14184
+ });
13598
14185
  return resolveSurface({
13599
14186
  kind: "section",
13600
- background: parseBackgroundContent(value)
14187
+ background: sectionForegroundBackground(background, context.theme.palette)
13601
14188
  }, {
13602
14189
  palette: context.theme.palette,
13603
14190
  buttonSystem: context.theme.buttons,
@@ -13622,6 +14209,70 @@ function surfaceInkStyles(foreground, palette) {
13622
14209
  };
13623
14210
  }
13624
14211
 
14212
+ //#endregion
14213
+ //#region ../blocks/src/system/fields/backgroundContent.ts
14214
+ /**
14215
+ * Replaces only the background medium while retaining presentation fields that
14216
+ * are valid for the resulting image variant. Stale color/gradient tab state is
14217
+ * never copied into the new value.
14218
+ */
14219
+ function replaceBackgroundWithImage(value, image, defaults = {}) {
14220
+ const current = parseBackgroundContent(value);
14221
+ let candidate;
14222
+ if (current?.type === "image") {
14223
+ const { type: _type, image: _image, ...presentation } = current;
14224
+ candidate = {
14225
+ ...defaults,
14226
+ ...presentation,
14227
+ type: "image",
14228
+ image
14229
+ };
14230
+ } else if (current?.type === "video") {
14231
+ const { type: _type, image: _image, showVideoOnMobile: _mobile, ...presentation } = current;
14232
+ candidate = {
14233
+ ...defaults,
14234
+ ...presentation,
14235
+ type: "image",
14236
+ image
14237
+ };
14238
+ } else {
14239
+ candidate = {
14240
+ ...defaults,
14241
+ ...commonPresentation(current),
14242
+ type: "image",
14243
+ image
14244
+ };
14245
+ }
14246
+ const canonical$1 = boxBackgroundSchema.parse(candidate);
14247
+ if (canonical$1.type !== "image") {
14248
+ throw new Error("Image background construction produced a non-image variant");
14249
+ }
14250
+ return canonical$1;
14251
+ }
14252
+ function parseBackgroundContent(value) {
14253
+ const result = boxBackgroundSchema.safeParse(value);
14254
+ return result.success ? result.data : null;
14255
+ }
14256
+ function commonPresentation(background) {
14257
+ return background ? {
14258
+ contentOwner: background.contentOwner,
14259
+ textColor: background.textColor,
14260
+ headingColor: background.headingColor,
14261
+ linkColor: background.linkColor,
14262
+ supportingColor: background.supportingColor
14263
+ } : {};
14264
+ }
14265
+ function parseBackgroundOverlay(value) {
14266
+ const result = boxBackgroundOverlaySchema.safeParse(value);
14267
+ return result.success ? result.data : undefined;
14268
+ }
14269
+ /** Rendering accepts explicit application sources separately from stored references. */
14270
+ const runtimeBackgroundContentSchema = runtimeBoxBackgroundSchema;
14271
+ function parseRuntimeBackgroundContent(value) {
14272
+ const result = runtimeBackgroundContentSchema.safeParse(value);
14273
+ return result.success ? result.data : null;
14274
+ }
14275
+
13625
14276
  //#endregion
13626
14277
  //#region ../blocks/src/system/transforms/registry/_utils/boxStyleClassNames.ts
13627
14278
  /**
@@ -17857,7 +18508,7 @@ function splitLayoutWith(children) {
17857
18508
  children
17858
18509
  }
17859
18510
  }),
17860
- renderBackgroundImage: false,
18511
+ renderBackgroundImage: heroPhotoPaintsGround("split"),
17861
18512
  baseClass: ""
17862
18513
  });
17863
18514
  }
@@ -17873,7 +18524,7 @@ const lookPhoto = el("figure", lookSlot("media"), [media({
17873
18524
  const lookCopy = el("div", lookSlot("copy"), heroContentNodes);
17874
18525
  const heroLookLayout = (children) => styledSection({
17875
18526
  baseClass: "",
17876
- renderBackgroundImage: false,
18527
+ renderBackgroundImage: heroPhotoPaintsGround("headline-row"),
17877
18528
  children: styledContainer({ children: el("div", lookSlot("frame"), children) })
17878
18529
  });
17879
18530
  const headlineRowLayout = heroLookLayout([
@@ -18921,8 +19572,24 @@ const blogListingBlockDefinition = {
18921
19572
  //#endregion
18922
19573
  //#region ../blocks/src/system/blocks/cta-full.ts
18923
19574
  const ctaComposition = composeFragments([{ fragment: ctaCopyFragment }, { fragment: ctaRowFragment }]);
19575
+ const figureLayout = styledSection({
19576
+ children: sectionContainer([el("div", lookSlot("layout"), [el("div", lookSlot("figure"), [text({
19577
+ as: "p",
19578
+ ...lookSlot("figure-value")
19579
+ }, when("content.figure"), bind("content.figure")), text({
19580
+ as: "p",
19581
+ ...lookSlot("figure-label")
19582
+ }, when("content.figureNote"), bind("content.figureNote"))], when("content.figure")), el("div", lookSlot("copy"), [...ctaComposition.layout])])], {
19583
+ gap: "md",
19584
+ className: "rb-relative"
19585
+ }),
19586
+ baseClass: ""
19587
+ });
18924
19588
  const ctaFullConfig = {
18925
- variantMeaning: { kind: "arrangement" },
19589
+ variantMeaning: {
19590
+ kind: "arrangement",
19591
+ lookOnlyKeys: ["figure"]
19592
+ },
18926
19593
  id: "block.cta-full",
18927
19594
  title: "Full-width CTA",
18928
19595
  category: "marketing",
@@ -18934,6 +19601,21 @@ const ctaFullConfig = {
18934
19601
  fragment: ctaRowFragment,
18935
19602
  fieldPriority: 2
18936
19603
  }],
19604
+ additionalFields: [parseFieldDefinition({
19605
+ id: "figure",
19606
+ type: "text",
19607
+ label: "Big figure",
19608
+ help: "A short number or price, like £12 or 6 weeks.",
19609
+ required: false,
19610
+ defaultValue: ""
19611
+ }), parseFieldDefinition({
19612
+ id: "figureNote",
19613
+ type: "text",
19614
+ label: "Under the figure",
19615
+ help: "A few words under it, like 'your first class'.",
19616
+ required: false,
19617
+ defaultValue: ""
19618
+ })],
18937
19619
  layout: styledSection({
18938
19620
  children: props(lookSlot("copy"))(sectionContainer(ctaComposition.layout, {
18939
19621
  gap: "md",
@@ -18941,6 +19623,7 @@ const ctaFullConfig = {
18941
19623
  })),
18942
19624
  baseClass: ""
18943
19625
  }),
19626
+ variants: { figure: figureLayout },
18944
19627
  description: "Centered call to action with optional background image.",
18945
19628
  tags: [
18946
19629
  "cta",
@@ -19786,6 +20469,21 @@ const splitHeadingColumnsLayout = styledSection({
19786
20469
  }),
19787
20470
  baseClass: ""
19788
20471
  });
20472
+ const openAnswer = el("div", lookSlot("item"), [text({
20473
+ as: "h3",
20474
+ ...lookSlot("item-title")
20475
+ }, bind("faqItem.question")), richText({ ...lookSlot("item-text") }, when("faqItem.answer"), bind("faqItem.answer"))], repeat("content.items", "faqItem", { indexName: "faqIndex" }));
20476
+ const openLayout = styledSection({
20477
+ children: styledContainer({
20478
+ baseClassName: "rb-relative rb-isolate",
20479
+ children: stack({
20480
+ gap: "lg",
20481
+ className: "rb-w-full",
20482
+ ...lookSlot("layout")
20483
+ }, [headingLayout, el("div", lookSlot("items"), [openAnswer])])
20484
+ }),
20485
+ baseClass: ""
20486
+ });
19789
20487
  const faqManifest = createBlockManifest({
19790
20488
  id: "block.faq",
19791
20489
  title: "FAQ",
@@ -19809,9 +20507,13 @@ const faqManifest = createBlockManifest({
19809
20507
  default: defaultLayout,
19810
20508
  "two-column": twoColumnLayout,
19811
20509
  "question-columns": questionColumnsLayout,
19812
- "split-heading-columns": splitHeadingColumnsLayout
20510
+ "split-heading-columns": splitHeadingColumnsLayout,
20511
+ open: openLayout
20512
+ },
20513
+ variantMeaning: {
20514
+ kind: "arrangement",
20515
+ lookOnlyKeys: ["open"]
19813
20516
  },
19814
- variantMeaning: { kind: "arrangement" },
19815
20517
  defaultVariant: "default",
19816
20518
  variantOptions: {
19817
20519
  default: {
@@ -24634,7 +25336,7 @@ const publicEventAttendanceModeSchema = z.enum(["in_person", "online"]);
24634
25336
  const publicEventAttendanceModeAvailabilitySchema = z.object({
24635
25337
  mode: publicEventAttendanceModeSchema,
24636
25338
  capacity: z.number().nullable(),
24637
- registeredCount: z.number(),
25339
+ registeredCount: z.number().nullable(),
24638
25340
  availableSpots: z.number().nullable(),
24639
25341
  waitlistEnabled: z.boolean()
24640
25342
  });
@@ -24799,7 +25501,7 @@ const publicEventBaseSchema = z.object({
24799
25501
  startsAt: z.string(),
24800
25502
  endsAt: z.string(),
24801
25503
  capacity: z.number().nullable(),
24802
- registeredCount: z.number(),
25504
+ registeredCount: z.number().nullable(),
24803
25505
  availableSpots: z.number().nullable(),
24804
25506
  waitlistEnabled: z.boolean(),
24805
25507
  venue: eventVenueSchema.nullable(),
@@ -25204,6 +25906,10 @@ function getEventAvailability(event) {
25204
25906
  isFull: false
25205
25907
  };
25206
25908
  }
25909
+ if (event.registeredCount === null) return {
25910
+ available: 0,
25911
+ isFull: true
25912
+ };
25207
25913
  const available = event.availableSpots ?? event.capacity - event.registeredCount;
25208
25914
  if (available <= 0) {
25209
25915
  return {
@@ -25259,12 +25965,12 @@ function lowCapacityThreshold(capacity) {
25259
25965
  }
25260
25966
  function getRemainingPlaces(source) {
25261
25967
  const { available, isFull: isFull$1 } = getEventAvailability(source);
25262
- if (available === null || source.capacity === null) return { kind: "unlimited" };
25968
+ if (available === null) return { kind: "unlimited" };
25263
25969
  if (isFull$1) return {
25264
25970
  kind: "full",
25265
25971
  waitlist: canJoinEventWaitlist(source)
25266
25972
  };
25267
- if (available <= lowCapacityThreshold(source.capacity)) {
25973
+ if (source.capacity !== null && available <= lowCapacityThreshold(source.capacity)) {
25268
25974
  return {
25269
25975
  kind: "low",
25270
25976
  remaining: available
@@ -25563,6 +26269,30 @@ function createHostedBlockApiConfig(input) {
25563
26269
  };
25564
26270
  }
25565
26271
 
26272
+ //#endregion
26273
+ //#region ../blocks/src/system/runtime/nodes/events/shared/eventScheduleSummary.ts
26274
+ function bookingDateLocale(siteTimeZone) {
26275
+ return inferSiteDateLocale(resolveBookingBusinessTimeZone(siteTimeZone));
26276
+ }
26277
+ /** A stored local clock has no date or DST offset; UTC preserves its digits. */
26278
+ function formatBookingSchedulePresentation(event) {
26279
+ const summary = event.scheduleSummary;
26280
+ if (!summary) return null;
26281
+ const start = summary.startTime ? parseUtcIsoDateTimeString(`1970-01-01T${summary.startTime}:00Z`) : null;
26282
+ const time = start?.ok ? formatTime$1(start.value, {
26283
+ timeZone: resolveBookingTimeZoneId("UTC"),
26284
+ locale: bookingDateLocale(event.siteTimeZone)
26285
+ }, { zone: "omit" }) : null;
26286
+ return {
26287
+ text: [summary.recurrenceDescription, time].filter(Boolean).join(", "),
26288
+ clock: time
26289
+ };
26290
+ }
26291
+ /** Text-only consumers delegate to the same authored-clock presentation. */
26292
+ function formatBookingScheduleSummary(event) {
26293
+ return formatBookingSchedulePresentation(event)?.text ?? null;
26294
+ }
26295
+
25566
26296
  //#endregion
25567
26297
  //#region ../blocks/src/system/runtime/nodes/events/domain/bookability.ts
25568
26298
  /**
@@ -27786,8 +28516,13 @@ function buildCalendarViewport(input) {
27786
28516
  }
27787
28517
  /** Tick digits are civil clock values, not session instants. */
27788
28518
  function formatEventHourLabel(hour, timeZone) {
27789
- return formatTime$1(toUtcIsoDateTimeString(`2000-01-01T${String(hour % 24).padStart(2, "0")}:00:00Z`), {
27790
- locale: eventDateLocale(timeZone),
28519
+ return formatEventClockLabel(`${String(hour % 24).padStart(2, "0")}:00`, eventDateLocale(timeZone));
28520
+ }
28521
+ /** Weekly cadence and tick clocks are civil values; UTC only carries their components. */
28522
+ function formatEventClockLabel(value, locale) {
28523
+ const clock = parseLocalScheduleTime(value);
28524
+ return formatTime$1(toUtcIsoDateTimeString(new Date(Date.UTC(2e3, 0, 1, clock.hour, clock.minute)).toISOString()), {
28525
+ locale,
27791
28526
  timeZone: resolveBookingBusinessTimeZone("UTC")
27792
28527
  }, { zone: "omit" });
27793
28528
  }
@@ -28206,9 +28941,9 @@ function findBookedEvent(events, identity) {
28206
28941
 
28207
28942
  //#endregion
28208
28943
  //#region ../blocks/src/system/runtime/nodes/event-registration/ticketSelectionLines.ts
28209
- /** Ticket selection requests a quote; only the server admits a booking. */
28210
- function canRequestTicketOption(availability) {
28211
- return availability.kind === "available" || availability.reason === "sold_out" || availability.reason === "overallCapacityUnavailable";
28944
+ /** Waitlists request places; bookings can only request currently available capacity. */
28945
+ function canRequestTicketOption(availability, purpose) {
28946
+ return availability.kind === "available" || purpose === "waitlist" && (availability.reason === "sold_out" || availability.reason === "overallCapacityUnavailable");
28212
28947
  }
28213
28948
  function normalizeTicketSelectionLines(lines) {
28214
28949
  const quantitiesByTicketType = new Map();
@@ -28236,8 +28971,20 @@ function getSingleSelectedPricingOptionId(lines) {
28236
28971
  function hasValidPricingOptionSelection(input) {
28237
28972
  if (!input.selectionRequired) return true;
28238
28973
  if (input.lines.length === 0) return false;
28239
- const availableOptionIds = new Set(input.visibleOptions.filter((option) => canRequestTicketOption(option.availability)).map((option) => option.pricingOptionId));
28240
- return input.lines.every((line) => line.pricingOptionId !== null && availableOptionIds.has(line.pricingOptionId));
28974
+ const totalSelectedQuantity = getTicketSelectionTotal(input.lines);
28975
+ return input.lines.every((line) => {
28976
+ const option = input.visibleOptions.find((option$1) => option$1.pricingOptionId === line.pricingOptionId);
28977
+ return option !== undefined && line.quantity <= getTicketTypeLineMax({
28978
+ currentQuantity: line.quantity,
28979
+ totalSelectedQuantity,
28980
+ maxTickets: input.maxTickets,
28981
+ purpose: input.purpose,
28982
+ pricingOptionId: option.pricingOptionId,
28983
+ availability: option.availability,
28984
+ heldPlacesBySelection: input.heldPlacesBySelection,
28985
+ overallRemainingPlaces: input.overallRemainingPlaces
28986
+ });
28987
+ });
28241
28988
  }
28242
28989
  function setTicketLineQuantity(input) {
28243
28990
  const nextQuantity = Math.max(0, Math.floor(input.quantity));
@@ -28251,9 +28998,19 @@ function setTicketLineQuantity(input) {
28251
28998
  }];
28252
28999
  }
28253
29000
  function getTicketTypeLineMax(input) {
29001
+ if (!canRequestTicketOption(input.availability, "waitlist")) return 0;
28254
29002
  const otherSelectedQuantity = input.totalSelectedQuantity - input.currentQuantity;
28255
29003
  const maxByBlock = input.maxTickets - otherSelectedQuantity;
28256
- return Math.max(0, maxByBlock);
29004
+ if (input.purpose === "waitlist") return Math.max(0, maxByBlock);
29005
+ const heldPlaces = input.heldPlacesBySelection ?? [];
29006
+ const heldForType = getTicketLineQuantity(heldPlaces, input.pricingOptionId);
29007
+ const publicRemaining = canRequestTicketOption(input.availability, input.purpose) ? input.availability.remainingPlaces ?? Infinity : 0;
29008
+ const maxByOverall = (input.overallRemainingPlaces ?? Infinity) + getTicketSelectionTotal(heldPlaces) - otherSelectedQuantity;
29009
+ return Math.max(0, Math.min(maxByBlock, publicRemaining + heldForType, maxByOverall));
29010
+ }
29011
+ /** Holds belong to the original event, mode and ticket types, not the edited draft. */
29012
+ function getHeldPlacesBySelection(current, held) {
29013
+ return held?.selectionId === current.selectionId && held?.attendanceMode === current.attendanceMode ? held.lines : [];
28257
29014
  }
28258
29015
 
28259
29016
  //#endregion
@@ -30174,20 +30931,6 @@ function formatTime$2(isoTimestamp, options) {
30174
30931
  const { context, zone } = bookingContext(options);
30175
30932
  return instant.ok ? formatTime$1(instant.value, context, { zone }) : isoTimestamp;
30176
30933
  }
30177
- function bookingDateLocale(siteTimeZone) {
30178
- return inferSiteDateLocale(resolveBookingBusinessTimeZone(siteTimeZone));
30179
- }
30180
- /** A stored local clock has no date or DST offset; UTC preserves its digits. */
30181
- function formatBookingScheduleSummary(event) {
30182
- const summary = event.scheduleSummary;
30183
- if (!summary) return null;
30184
- const start = summary.startTime ? parseUtcIsoDateTimeString(`1970-01-01T${summary.startTime}:00Z`) : null;
30185
- const time = start?.ok ? formatTime$1(start.value, {
30186
- timeZone: resolveBookingTimeZoneId("UTC"),
30187
- locale: bookingDateLocale(event.siteTimeZone)
30188
- }, { zone: "omit" }) : null;
30189
- return [summary.recurrenceDescription, time].filter(Boolean).join(", ");
30190
- }
30191
30934
  /** Variant meaning is closed here; core owns the resulting typography. */
30192
30935
  const BOOKING_DATE_PRESENTATIONS = {
30193
30936
  day: {
@@ -30436,10 +31179,34 @@ function getVisibleTicketTypes(input) {
30436
31179
  pricingOptionId: id
30437
31180
  })) : [];
30438
31181
  const source = input.eligibility?.ticketTypes ?? eventTicketTypes;
30439
- return source.filter((ticketType) => ticketType.isActive && (!input.selectedAttendanceMode || (ticketType.attendanceScope ?? "both") === "both" || (ticketType.attendanceScope ?? "both") === input.selectedAttendanceMode));
31182
+ const selectedIds = new Set(input.ticketSelections?.map((line) => line.pricingOptionId));
31183
+ return source.map((ticketType) => ticketTypeForSelectionMode(ticketType, input.selectedAttendanceMode)).filter((ticketType) => selectedIds.has(ticketType.pricingOptionId) || ticketType.isActive && ticketTypeMatchesMode(ticketType, input.selectedAttendanceMode));
31184
+ }
31185
+ function ticketTypeForSelectionMode(ticketType, mode) {
31186
+ if (!ticketType.isActive) return {
31187
+ ...ticketType,
31188
+ availability: {
31189
+ kind: "unavailable",
31190
+ reason: "inactive",
31191
+ remainingPlaces: ticketType.availability.remainingPlaces
31192
+ }
31193
+ };
31194
+ if (!ticketTypeMatchesMode(ticketType, mode)) return {
31195
+ ...ticketType,
31196
+ availability: {
31197
+ kind: "unavailable",
31198
+ reason: "attendance_mode_unavailable",
31199
+ remainingPlaces: ticketType.availability.remainingPlaces
31200
+ }
31201
+ };
31202
+ return ticketType;
30440
31203
  }
30441
- function getImplicitSingleTicketType(ticketTypes) {
30442
- return ticketTypes.length === 1 && ticketTypes[0] && canRequestTicketOption(ticketTypes[0].availability) ? ticketTypes[0] : null;
31204
+ function ticketTypeMatchesMode(ticketType, mode) {
31205
+ const scope = ticketType.attendanceScope ?? "both";
31206
+ return !mode || scope === "both" || scope === mode;
31207
+ }
31208
+ function getImplicitSingleTicketType(ticketTypes, purpose = "book") {
31209
+ return ticketTypes.length === 1 && ticketTypes[0] && canRequestTicketOption(ticketTypes[0].availability, purpose) ? ticketTypes[0] : null;
30443
31210
  }
30444
31211
  function findSelectedTicketType(input) {
30445
31212
  if (!input.selectedPricingOptionId) {
@@ -32786,6 +33553,27 @@ function getEligibilityFlowFlags(input) {
32786
33553
  isMembershipFastTrack: input.portalAuthState === "authenticated" && hasMembership && input.ticketsCount === 1 && input.paymentMethod === "membership"
32787
33554
  };
32788
33555
  }
33556
+ /** Prefer refreshed capacity, including a known zero; fall back while options load. */
33557
+ function getTicketSelectionRemainingPlaces(input) {
33558
+ const availability = input.eligibility?.availability;
33559
+ if (availability && "remainingPlaces" in availability) return availability.remainingPlaces ?? null;
33560
+ return input.event ? getEventAvailability(selectEventCapacitySource(input.event, input.attendanceMode)).available : null;
33561
+ }
33562
+ function resolveHeldTicketPlaces(current, invitation, paymentHold) {
33563
+ const held = invitation.kind === "ready" ? {
33564
+ selectionId: invitation.restoration.event?.selectionId ?? current.selectionId,
33565
+ attendanceMode: invitation.restoration.attendanceMode,
33566
+ lines: invitation.restoration.ticketSelections
33567
+ } : paymentHold;
33568
+ return getHeldPlacesBySelection(current, held);
33569
+ }
33570
+ function getCurrentTicketSelection(event, attendanceMode, lines) {
33571
+ return {
33572
+ selectionId: event?.selectionId ?? null,
33573
+ attendanceMode,
33574
+ lines
33575
+ };
33576
+ }
32789
33577
 
32790
33578
  //#endregion
32791
33579
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationDraft.state.ts
@@ -35068,9 +35856,7 @@ function buildConfirmationPaymentCandidate(input) {
35068
35856
  default: return assertNever$7(input);
35069
35857
  }
35070
35858
  }
35071
- /** Public payment evidence returns only schema-valid decisions. Legacy email
35072
- * snapshots deliberately use the amount calculation below instead of this boundary.
35073
- */
35859
+ /** Persisted booking payment evidence returns only schema-valid decisions. */
35074
35860
  function buildConfirmationPayment(input) {
35075
35861
  const payment = buildConfirmationPaymentCandidate(input);
35076
35862
  if (!payment.ok) return payment;
@@ -35081,24 +35867,6 @@ function buildConfirmationPayment(input) {
35081
35867
  function settledOrderItemTotalCents(item) {
35082
35868
  return effectiveOrderItemNetTotalCents(item) + (item.creditCents ?? 0);
35083
35869
  }
35084
- /** Historical notification amounts, not a validated public payment decision.
35085
- * Preserve HEAD's raw snapshot numbers; strict evidence validation belongs to
35086
- * the public read boundary, not notification-plan construction (#1587).
35087
- */
35088
- function calculateOrderItemsConfirmationPaymentAmounts(input) {
35089
- const totalCents = input.items.reduce((total, item) => total + settledOrderItemTotalCents(item), 0);
35090
- const amountPaidCents = input.settlement.kind === "settled_in_full" ? totalCents : input.settlement.collectedCents;
35091
- return {
35092
- amountPaidCents,
35093
- amountOutstandingCents: calculateOutstandingOrderAmountCents({
35094
- totalCents,
35095
- settledCents: amountPaidCents
35096
- })
35097
- };
35098
- }
35099
- function collectedOrderItemsConfirmationCents(items, collectedCentsByOrderItemId) {
35100
- return items.reduce((total, item) => total + (collectedCentsByOrderItemId[item.id] ?? 0), 0);
35101
- }
35102
35870
 
35103
35871
  //#endregion
35104
35872
  //#region ../commerce/src/purchase/confirmationEmailPayment.ts
@@ -35160,56 +35928,9 @@ function toConfirmationEmailPaymentSummary(payment) {
35160
35928
  default: return assertNever$7(payment);
35161
35929
  }
35162
35930
  }
35163
- /** Legacy notification adapter, retained unchanged in meaning until #1587. */
35164
- function buildOrderItemsConfirmationEmailPaymentSummary(options) {
35165
- switch (options.orderPaymentMethod) {
35166
- case "stripe":
35167
- case "store_credit":
35168
- case "mixed": {
35169
- const amounts = calculateOrderItemsConfirmationPaymentAmounts({
35170
- items: options.items,
35171
- settlement: options.settlement
35172
- });
35173
- return {
35174
- kind: "monetary",
35175
- amountPaidCents: amounts.amountPaidCents,
35176
- ...options.settlement.kind === "settled_in_full" ? {} : {
35177
- amountOutstandingCents: amounts.amountOutstandingCents,
35178
- dueAt: options.flexibleBalanceDueAt ?? null
35179
- },
35180
- currency: options.currency,
35181
- paymentMethod: options.orderPaymentMethod
35182
- };
35183
- }
35184
- case "pass_redemption": return { kind: "pass" };
35185
- case "membership_access": return { kind: "membership" };
35186
- case "manual": return options.flexibleBalanceDueAt ? {
35187
- kind: "managed_balance_due",
35188
- dueAt: options.flexibleBalanceDueAt
35189
- } : { kind: "manual_due" };
35190
- case "waived": return { kind: "waived" };
35191
- case "free": return { kind: "free" };
35192
- default: return assertNever$7(options.orderPaymentMethod);
35193
- }
35194
- }
35195
- function buildOrderItemConfirmationEmailPaymentSummary(options) {
35196
- return buildOrderItemsConfirmationEmailPaymentSummary({
35197
- ...options,
35198
- items: [options.item]
35199
- });
35200
- }
35201
- function buildEventRegistrationConfirmationEmailPaymentSummary(options) {
35202
- return buildNonMonetaryConfirmationEmailPaymentSummary({
35203
- paymentMethod: options.paymentMethod,
35204
- passCreditsUsed: options.passCreditsUsed
35205
- });
35206
- }
35207
35931
  function buildManualAttendeeConfirmationEmailPaymentSummary(options) {
35208
35932
  return buildNonMonetaryConfirmationEmailPaymentSummary(options);
35209
35933
  }
35210
- function buildBundleConfirmationEmailPaymentSummary(options) {
35211
- return buildNonMonetaryConfirmationEmailPaymentSummary(options);
35212
- }
35213
35934
  function buildTransferredAttendeeConfirmationEmailPaymentSummary(options) {
35214
35935
  switch (options.paymentMethod) {
35215
35936
  case "free":
@@ -35223,34 +35944,6 @@ function buildTransferredAttendeeConfirmationEmailPaymentSummary(options) {
35223
35944
  }
35224
35945
  return assertNever$7(options.paymentMethod);
35225
35946
  }
35226
- function buildEventSeriesRegistrationConfirmationEmailPaymentSummary(options) {
35227
- switch (options.paymentMethod) {
35228
- case "free":
35229
- case "membership": return buildNonMonetaryConfirmationEmailPaymentSummary({
35230
- paymentMethod: options.paymentMethod,
35231
- membershipName: options.membershipName
35232
- });
35233
- case "stripe": return buildMonetaryConfirmationEmailPaymentSummary({
35234
- amountPaidCents: options.amountPaidCents,
35235
- currency: options.currency,
35236
- paymentMethod: "stripe"
35237
- });
35238
- }
35239
- return assertNever$7(options.paymentMethod);
35240
- }
35241
- function buildBypassedCheckoutConfirmationEmailPaymentSummary(options) {
35242
- switch (options.bypassReason) {
35243
- case "free":
35244
- case "discount_free": return { kind: "free" };
35245
- case "store_credit": return {
35246
- kind: "monetary",
35247
- amountPaidCents: options.settledAmountCents,
35248
- currency: options.currency,
35249
- paymentMethod: "store_credit"
35250
- };
35251
- }
35252
- return assertNever$7(options.bypassReason);
35253
- }
35254
35947
  function toConfirmationEmailTemplatePaymentFields(summary) {
35255
35948
  if (!summary) {
35256
35949
  return {};
@@ -35814,170 +36507,143 @@ function resolveInstalmentPlanIntroText(input) {
35814
36507
 
35815
36508
  //#endregion
35816
36509
  //#region ../commerce/src/purchase/orderCompletionEmailPlan.ts
35817
- function buildEventConfirmationAction(items, options) {
35818
- const firstItem = items[0];
35819
- if (!firstItem) {
35820
- throw new Error("Cannot build an event confirmation without order items");
35821
- }
35822
- const settlement = options.settlement;
35823
- const paymentSummary = buildOrderItemsConfirmationEmailPaymentSummary({
35824
- orderPaymentMethod: options.orderPaymentMethod,
35825
- items,
35826
- currency: options.currency,
35827
- flexibleBalanceDueAt: options.flexibleBalanceDueAt,
35828
- settlement: settlement.kind === "settled_in_full" ? settlement : {
35829
- kind: "collected",
35830
- collectedCents: collectedOrderItemsConfirmationCents(items, settlement.collectedCentsByOrderItemId)
35831
- }
35832
- });
35833
- return {
35834
- kind: "send_event_confirmation",
35835
- attendeeId: firstItem.eventAttendeeId,
35836
- metadata: {
35837
- ...firstItem.metadata,
35838
- ticketCount: items.reduce((total, item) => total + item.metadata.ticketCount, 0)
35839
- },
35840
- paymentSummary
35841
- };
35842
- }
35843
36510
  function hasPassBundleFulfillment(metadata) {
35844
36511
  return !!metadata.bundleFulfillment;
35845
36512
  }
35846
36513
  function hasMembershipBundleFulfillment(metadata) {
35847
36514
  return !!metadata.bundleFulfillment;
35848
36515
  }
35849
- function buildOrderCompletionEmailPlan(options) {
35850
- const firstItem = options.items[0];
35851
- if (!firstItem) {
35852
- return {
36516
+ const noActions = () => ({
36517
+ actions: [],
36518
+ warnings: []
36519
+ });
36520
+ function planEventItem(item, input) {
36521
+ const id = item.kind === "event_ticket" ? item.eventAttendeeId : item.eventSeriesRegistrationId;
36522
+ if (!id) return {
36523
+ actions: [],
36524
+ warnings: [{
36525
+ kind: item.kind === "event_ticket" ? "missing_event_attendee_id" : "missing_event_series_registration_id",
36526
+ orderId: item.orderId,
36527
+ orderItemId: item.id
36528
+ }]
36529
+ };
36530
+ const facts = input.bookingFacts.get(id);
36531
+ if (!facts) return noActions();
36532
+ if (item.kind === "event_ticket" && item.eventAttendeeId) return {
36533
+ warnings: [],
36534
+ actions: [{
36535
+ kind: "send_event_confirmation",
36536
+ facts,
36537
+ metadata: item.metadata
36538
+ }]
36539
+ };
36540
+ return {
36541
+ warnings: [],
36542
+ actions: [{
36543
+ kind: "send_event_series_registration_confirmation",
36544
+ orderId: item.orderId,
36545
+ facts
36546
+ }]
36547
+ };
36548
+ }
36549
+ function planPassItem(item, input) {
36550
+ if (!hasPassBundleFulfillment(item.metadata)) return noActions();
36551
+ if (!item.eventAttendeeId) return {
36552
+ actions: [],
36553
+ warnings: [{
36554
+ kind: "missing_event_attendee_id",
36555
+ orderId: item.orderId,
36556
+ orderItemId: item.id
36557
+ }]
36558
+ };
36559
+ const facts = input.bookingFacts.get(item.eventAttendeeId);
36560
+ return facts ? {
36561
+ warnings: [],
36562
+ actions: [{
36563
+ kind: "send_pass_bundle_confirmation",
36564
+ facts,
36565
+ metadata: item.metadata
36566
+ }]
36567
+ } : noActions();
36568
+ }
36569
+ function planMembershipItem(item, input) {
36570
+ if (hasMembershipBundleFulfillment(item.metadata)) {
36571
+ if (!item.eventAttendeeId) return {
35853
36572
  actions: [],
35854
- warnings: []
36573
+ warnings: [{
36574
+ kind: "missing_event_attendee_id",
36575
+ orderId: item.orderId,
36576
+ orderItemId: item.id
36577
+ }]
35855
36578
  };
36579
+ const facts = input.bookingFacts.get(item.eventAttendeeId);
36580
+ return facts ? {
36581
+ warnings: [],
36582
+ actions: [{
36583
+ kind: "send_membership_bundle_confirmation",
36584
+ facts,
36585
+ metadata: item.metadata
36586
+ }]
36587
+ } : noActions();
36588
+ }
36589
+ if (!item.customerMembershipId) return {
36590
+ actions: [],
36591
+ warnings: [{
36592
+ kind: "missing_customer_membership_id",
36593
+ orderId: item.orderId,
36594
+ orderItemId: item.id
36595
+ }]
36596
+ };
36597
+ return {
36598
+ warnings: [],
36599
+ actions: [{
36600
+ kind: "send_membership_activation",
36601
+ customerMembershipId: item.customerMembershipId,
36602
+ amountPaidCents: settledOrderItemTotalCents(item)
36603
+ }]
36604
+ };
36605
+ }
36606
+ function planItem(item, input) {
36607
+ switch (item.kind) {
36608
+ case "event_ticket":
36609
+ case "event_series_registration": return planEventItem(item, input);
36610
+ case "pass": return planPassItem(item, input);
36611
+ case "membership": return planMembershipItem(item, input);
36612
+ case "appointment": return item.appointmentId ? {
36613
+ warnings: [],
36614
+ actions: [{
36615
+ kind: "send_appointment_confirmation",
36616
+ appointmentId: item.appointmentId
36617
+ }]
36618
+ } : {
36619
+ actions: [],
36620
+ warnings: [{
36621
+ kind: "missing_appointment_id",
36622
+ orderId: item.orderId,
36623
+ orderItemId: item.id
36624
+ }]
36625
+ };
36626
+ case "product": return noActions();
35856
36627
  }
35857
- const actions$1 = [];
35858
- const warnings = [];
35859
- const plannedEventAttendeeIds = new Set();
35860
- for (const item of options.items) {
35861
- switch (item.kind) {
35862
- case "event_ticket":
35863
- if (!item.eventAttendeeId) {
35864
- warnings.push({
35865
- kind: "missing_event_attendee_id",
35866
- orderItemId: item.id,
35867
- orderId: item.orderId
35868
- });
35869
- break;
35870
- }
35871
- if (plannedEventAttendeeIds.has(item.eventAttendeeId)) {
35872
- break;
35873
- }
35874
- plannedEventAttendeeIds.add(item.eventAttendeeId);
35875
- actions$1.push(buildEventConfirmationAction(options.items.filter((candidate) => candidate.kind === "event_ticket" && candidate.eventAttendeeId === item.eventAttendeeId), {
35876
- orderPaymentMethod: options.orderPaymentMethod,
35877
- currency: options.currency,
35878
- flexibleBalanceDueAt: options.flexibleBalanceDueAt ?? null,
35879
- settlement: options.settlement
35880
- }));
35881
- break;
35882
- case "pass":
35883
- if (hasPassBundleFulfillment(item.metadata)) {
35884
- if (!item.eventAttendeeId) {
35885
- warnings.push({
35886
- kind: "missing_event_attendee_id",
35887
- orderItemId: item.id,
35888
- orderId: item.orderId
35889
- });
35890
- break;
35891
- }
35892
- actions$1.push({
35893
- kind: "send_pass_bundle_confirmation",
35894
- attendeeId: item.eventAttendeeId,
35895
- metadata: item.metadata
35896
- });
35897
- }
35898
- break;
35899
- case "event_series_registration":
35900
- if (!item.eventSeriesRegistrationId) {
35901
- warnings.push({
35902
- kind: "missing_event_series_registration_id",
35903
- orderItemId: item.id,
35904
- orderId: item.orderId
35905
- });
35906
- break;
35907
- }
35908
- actions$1.push({
35909
- kind: "send_event_series_registration_confirmation",
35910
- orderId: item.orderId,
35911
- registrationId: item.eventSeriesRegistrationId,
35912
- paymentSummary: buildOrderItemConfirmationEmailPaymentSummary({
35913
- orderPaymentMethod: options.orderPaymentMethod,
35914
- item,
35915
- currency: options.currency,
35916
- flexibleBalanceDueAt: options.flexibleBalanceDueAt ?? null,
35917
- settlement: options.settlement.kind === "settled_in_full" ? options.settlement : {
35918
- kind: "collected",
35919
- collectedCents: options.settlement.collectedCentsByOrderItemId[item.id] ?? 0
35920
- }
35921
- })
35922
- });
35923
- break;
35924
- case "appointment":
35925
- if (!item.appointmentId) {
35926
- warnings.push({
35927
- kind: "missing_appointment_id",
35928
- orderItemId: item.id,
35929
- orderId: item.orderId
35930
- });
35931
- break;
35932
- }
35933
- actions$1.push({
35934
- kind: "send_appointment_confirmation",
35935
- appointmentId: item.appointmentId
35936
- });
35937
- break;
35938
- case "membership":
35939
- if (hasMembershipBundleFulfillment(item.metadata)) {
35940
- if (!item.eventAttendeeId) {
35941
- warnings.push({
35942
- kind: "missing_event_attendee_id",
35943
- orderItemId: item.id,
35944
- orderId: item.orderId
35945
- });
35946
- break;
35947
- }
35948
- actions$1.push({
35949
- kind: "send_membership_bundle_confirmation",
35950
- attendeeId: item.eventAttendeeId,
35951
- metadata: item.metadata
35952
- });
35953
- break;
35954
- }
35955
- if (!item.customerMembershipId) {
35956
- warnings.push({
35957
- kind: "missing_customer_membership_id",
35958
- orderItemId: item.id,
35959
- orderId: item.orderId
35960
- });
35961
- break;
35962
- }
35963
- actions$1.push({
35964
- kind: "send_membership_activation",
35965
- customerMembershipId: item.customerMembershipId,
35966
- amountPaidCents: settledOrderItemTotalCents(item)
35967
- });
35968
- break;
35969
- case "product": break;
35970
- }
35971
- }
35972
- if (options.items.some((item) => item.kind === "product" || item.kind === "pass")) {
35973
- actions$1.unshift({
35974
- kind: "send_order_confirmation",
35975
- orderId: firstItem.orderId
35976
- });
35977
- }
36628
+ }
36629
+ function bookingGroupId(item) {
36630
+ return item.eventAttendeeId ?? item.eventSeriesRegistrationId ?? null;
36631
+ }
36632
+ /** Complete booking facts are already decided; purchase receipts remain separate. */
36633
+ function buildOrderCompletionEmailPlan(input) {
36634
+ const first = input.items[0];
36635
+ if (!first) return noActions();
36636
+ const plans = input.items.filter((item, index) => {
36637
+ const id = bookingGroupId(item);
36638
+ return id === null || !input.items.slice(0, index).some((previous) => bookingGroupId(previous) === id);
36639
+ }).map((item) => planItem(item, input));
36640
+ const receipt = input.items.some((item) => item.kind === "product" || item.kind === "pass") ? [{
36641
+ kind: "send_order_confirmation",
36642
+ orderId: first.orderId
36643
+ }] : [];
35978
36644
  return {
35979
- actions: actions$1,
35980
- warnings
36645
+ actions: [...receipt, ...plans.flatMap((plan) => plan.actions)],
36646
+ warnings: plans.flatMap((plan) => plan.warnings)
35981
36647
  };
35982
36648
  }
35983
36649
 
@@ -38389,6 +39055,9 @@ function getStepLabel(step) {
38389
39055
  function visitorHoldsOwnPlace(input) {
38390
39056
  return input.hasWaitlistClaim || input.retryingHeldPlace;
38391
39057
  }
39058
+ function getTicketSelectionPurpose(admission) {
39059
+ return admission.kind === "waitlist" ? "waitlist" : "book";
39060
+ }
38392
39061
  function resolveRegistrationAdmission(input) {
38393
39062
  if (input.holdsOwnPlace) return { kind: "resume" };
38394
39063
  if (input.eligibilityLoading) return { kind: "checking" };
@@ -40939,6 +41608,15 @@ const eventRegistrationManifest = createBlockManifest({
40939
41608
  ], { gap: "lg" })
40940
41609
  })]
40941
41610
  });
41611
+ const registrationEventScopeParams = {
41612
+ detailAdmission: { $bind: { from: "$root.subject.detailAdmission" } },
41613
+ seriesId: { $bind: { from: "$root.subject.eventSeriesId" } },
41614
+ seriesIds: { $bind: {
41615
+ from: "$root.subject.catalogueEventSeriesIds",
41616
+ transforms: [{ id: "value.csv" }]
41617
+ } },
41618
+ runSeriesId: { $bind: { from: "$root.entrySubrouteContext.run.seriesId" } }
41619
+ };
40942
41620
  const eventRegistrationBlockDefinition = {
40943
41621
  manifest: eventRegistrationManifest,
40944
41622
  runtime: eventRegistrationBlockRuntime,
@@ -40965,12 +41643,7 @@ const eventRegistrationBlockDefinition = {
40965
41643
  fallback: "published"
40966
41644
  } },
40967
41645
  readProfile: "registration",
40968
- seriesId: { $bind: { from: "$root.subject.eventSeriesId" } },
40969
- seriesIds: { $bind: {
40970
- from: "$root.subject.catalogueEventSeriesIds",
40971
- transforms: [{ id: "value.csv" }]
40972
- } },
40973
- runSeriesId: { $bind: { from: "$root.entrySubrouteContext.run.seriesId" } },
41646
+ ...registrationEventScopeParams,
40974
41647
  includeOccurrenceId: { $bind: { from: "$root.occurrenceContext.id" } }
40975
41648
  },
40976
41649
  mode: "server"
@@ -40990,12 +41663,7 @@ const eventRegistrationBlockDefinition = {
40990
41663
  fallback: "published"
40991
41664
  } },
40992
41665
  readProfile: "registration",
40993
- seriesId: { $bind: { from: "$root.subject.eventSeriesId" } },
40994
- seriesIds: { $bind: {
40995
- from: "$root.subject.catalogueEventSeriesIds",
40996
- transforms: [{ id: "value.csv" }]
40997
- } },
40998
- runSeriesId: { $bind: { from: "$root.entrySubrouteContext.run.seriesId" } }
41666
+ ...registrationEventScopeParams
40999
41667
  },
41000
41668
  mode: "server"
41001
41669
  },
@@ -41472,6 +42140,7 @@ const eventDetailsBlockDefinition = {
41472
42140
  } },
41473
42141
  limit: "100",
41474
42142
  readProfile: "details",
42143
+ detailAdmission: { $bind: { from: "$root.subject.detailAdmission" } },
41475
42144
  seriesId: { $bind: { from: "$root.subject.eventSeriesId" } },
41476
42145
  seriesIds: { $bind: {
41477
42146
  from: "$root.subject.catalogueEventSeriesIds",
@@ -47422,6 +48091,7 @@ const recurringScheduleLayout = styledSection({
47422
48091
  fallback: "secondary"
47423
48092
  } }
47424
48093
  }, [text({}, bind("content.seeAllLabel"))], when("content.seeAllUrl"))]), el("recurring-schedule", {
48094
+ dateContext: { $bind: { from: "$root.dateContext" } },
47425
48095
  schedule: { $bind: { from: "data.schedule" } },
47426
48096
  buttonText: { $bind: {
47427
48097
  from: "content.buttonText",
@@ -50378,7 +51048,12 @@ const contentEntrySchema = z.object({
50378
51048
  id: z.string().nullable().optional(),
50379
51049
  slug: z.string().nullable().optional()
50380
51050
  }).nullable();
51051
+ const shopDateContextSchema = z.object({
51052
+ locale: z.string(),
51053
+ timeZone: z.string().transform(resolveBookingBusinessTimeZone)
51054
+ });
50381
51055
  const shopSourceSchema = z.object({
51056
+ dateContext: shopDateContextSchema.optional(),
50382
51057
  ...cardListPresentationSchemaShape,
50383
51058
  featured: z.string().nullable().optional(),
50384
51059
  className: z.string().nullable().optional(),
@@ -50543,6 +51218,7 @@ const checkoutDisplaySchema = z.object({
50543
51218
  submitButtonText: z.string()
50544
51219
  });
50545
51220
  const passesMembershipsHydrationSchema = z.object({
51221
+ dateContext: shopDateContextSchema,
50546
51222
  presentation: z.object(cardListPresentationSchemaShape).optional(),
50547
51223
  featured: z.string().optional(),
50548
51224
  memberships: z.array(publicMembershipProductSchema),
@@ -50623,6 +51299,7 @@ const shopBlockRuntime = defineProgressiveBlockRuntime({
50623
51299
  const passPurchaseMode = source.passPurchaseMode ?? "add_to_cart";
50624
51300
  const subscribeButtonText = source.subscribeButtonText ?? "Subscribe";
50625
51301
  const products = shopDisplayedProducts(source);
51302
+ const dateContext = source.dateContext ?? resolveDateRuntimeContext(undefined);
50626
51303
  return {
50627
51304
  display: buildPassesMembershipsDisplay({
50628
51305
  cardList: source.lookSlots !== undefined,
@@ -50643,6 +51320,10 @@ const shopBlockRuntime = defineProgressiveBlockRuntime({
50643
51320
  hydration: {
50644
51321
  ...source.lookSlots ? { presentation: shopCardListPresentation(source) } : {},
50645
51322
  ...shopFeaturedProduct(source, products),
51323
+ dateContext: {
51324
+ locale: dateContext.locale,
51325
+ timeZone: dateContext.timeZone
51326
+ },
50646
51327
  memberships: products.memberships,
50647
51328
  passPurchaseMode,
50648
51329
  buttonVariant: source.buttonVariant ?? "primary",
@@ -51167,6 +51848,7 @@ const shopManifest = createBlockManifest({
51167
51848
  featured: { $bind: { from: "content.featured" } },
51168
51849
  readAuthority: { $bind: { from: "$root.readAuthority" } },
51169
51850
  siteTerms: { $bind: { from: "$root.siteTerms" } },
51851
+ dateContext: { $bind: { from: "$root.dateContext" } },
51170
51852
  demoEmailSuppressed: { $bind: {
51171
51853
  from: "$root.demoEmailSuppressed",
51172
51854
  fallback: false
@@ -53062,6 +53744,7 @@ function describeUnresolvedLocationMapLocation(resolvedLocation) {
53062
53744
  }
53063
53745
  }
53064
53746
  function normalizeLocationMapProps(input) {
53747
+ const parsedHours = parseWeeklyOpeningHours(input.openingHours);
53065
53748
  const resolvedLocation = resolveLocationMapLocation(input);
53066
53749
  const locationValue = resolvedLocation.kind === "unresolved" ? null : resolvedLocation.locationValue;
53067
53750
  const directionsLabel = normalizeLocationAddressText(input.directionsLabel) ?? LOCATION_MAP_DEFAULTS.directionsLabel;
@@ -53073,6 +53756,8 @@ function normalizeLocationMapProps(input) {
53073
53756
  location: toLocationCoordinates(locationValue),
53074
53757
  zoom: Math.min(LOCATION_MAP_LIMITS.zoom.max, Math.max(LOCATION_MAP_LIMITS.zoom.min, Math.round(normalizeNumber(input.zoom, LOCATION_MAP_DEFAULTS.zoom)))),
53075
53758
  heightPx: Math.min(LOCATION_MAP_LIMITS.heightPx.max, Math.max(LOCATION_MAP_LIMITS.heightPx.min, Math.round(normalizeNumber(input.heightPx, LOCATION_MAP_DEFAULTS.heightPx)))),
53759
+ openingHours: parsedHours.ok ? parsedHours.value : null,
53760
+ showOpeningHours: normalizeBoolean$1(input.showOpeningHours, true),
53076
53761
  showAddressText: normalizeBoolean$1(input.showAddressText, LOCATION_MAP_DEFAULTS.showAddressText),
53077
53762
  showDirections: normalizeBoolean$1(input.showDirections, LOCATION_MAP_DEFAULTS.showDirections),
53078
53763
  directionsLabel,
@@ -53085,6 +53770,8 @@ function toLocationMapHydrationProps(input) {
53085
53770
  location: normalized.locationValue,
53086
53771
  zoom: normalized.zoom,
53087
53772
  heightPx: normalized.heightPx,
53773
+ openingHours: normalized.openingHours,
53774
+ showOpeningHours: normalized.showOpeningHours,
53088
53775
  showAddressText: normalized.showAddressText,
53089
53776
  showDirections: normalized.showDirections,
53090
53777
  directionsLabel: normalized.directionsLabel,
@@ -53122,6 +53809,7 @@ function normalizeLocationMapContent(content) {
53122
53809
  heading: typeof record.heading === "string" ? record.heading : undefined,
53123
53810
  locationSource,
53124
53811
  ...authoredLocationMapSourceFields(locationSource, venueId, normalized.locationValue),
53812
+ showOpeningHours: record.showOpeningHours !== false,
53125
53813
  showAddressText: normalized.showAddressText,
53126
53814
  showDirections: normalized.showDirections,
53127
53815
  directionsLabel: normalized.directionsLabel,
@@ -53255,6 +53943,13 @@ const locationMapManifest = createBlockManifest({
53255
53943
  }
53256
53944
  ] }
53257
53945
  }),
53946
+ parseFieldDefinition({
53947
+ id: "showOpeningHours",
53948
+ type: "boolean",
53949
+ label: "Show opening hours",
53950
+ defaultValue: true,
53951
+ ui: { intent: "display" }
53952
+ }),
53258
53953
  parseFieldDefinition({
53259
53954
  id: "showAddressText",
53260
53955
  type: "boolean",
@@ -53316,6 +54011,14 @@ const locationMapManifest = createBlockManifest({
53316
54011
  baseClass: "",
53317
54012
  spacing: "comfortable",
53318
54013
  children: sectionContainer([text({ as: "h2" }, bind("content.heading"), when("content.heading")), el("location-map", {
54014
+ openingHours: { $bind: {
54015
+ from: "$root.openingHours",
54016
+ fallback: null
54017
+ } },
54018
+ showOpeningHours: { $bind: {
54019
+ from: "content.showOpeningHours",
54020
+ fallback: true
54021
+ } },
53319
54022
  location: { $bind: {
53320
54023
  from: "content.location",
53321
54024
  fallback: null
@@ -53368,6 +54071,7 @@ const locationMapBlockDefinition = {
53368
54071
  "venueId",
53369
54072
  "location",
53370
54073
  "showAddressText",
54074
+ "showOpeningHours",
53371
54075
  "showDirections",
53372
54076
  "directionsLabel",
53373
54077
  "zoom",
@@ -58504,6 +59208,7 @@ function validateAppointmentBookingFormSource(input) {
58504
59208
  }
58505
59209
  function normalizeAppointmentBookingServices(input) {
58506
59210
  return input.services.map((service) => ({
59211
+ ...service.image === undefined ? {} : { image: service.image },
58507
59212
  id: service.id,
58508
59213
  siteId: input.siteId,
58509
59214
  title: service.title,
@@ -58598,6 +59303,7 @@ const blockBookingPaymentTermsAvailabilitySchema = z.union([z.object({
58598
59303
  reason: z.literal("online_payment_configured_before_launch")
58599
59304
  })]);
58600
59305
  const bookingServiceSchema = z.object({
59306
+ image: runtimeMediaSchema.nullable().optional(),
58601
59307
  location: z.object({ mode: z.enum([
58602
59308
  "online",
58603
59309
  "in_person",
@@ -59103,7 +59809,7 @@ function resolveEventDetailsPresentation(input) {
59103
59809
  //#region ../blocks/src/system/blocks/offerings-preview/scheduleSummary.ts
59104
59810
  function deriveEventScheduleSummary(event) {
59105
59811
  const occurrence = occurrenceFromEvent(event);
59106
- return localDayNumber(occurrence.endsAt, occurrence.timeZone) > localDayNumber(occurrence.startsAt, occurrence.timeZone) ? {
59812
+ return compareLocalScheduleDates(getLocalScheduleDate(new Date(occurrence.endsAt), resolveBookingBusinessTimeZone(occurrence.timeZone)), getLocalScheduleDate(new Date(occurrence.startsAt), resolveBookingBusinessTimeZone(occurrence.timeZone))) > 0 ? {
59107
59813
  kind: "multi-day-span",
59108
59814
  occurrence
59109
59815
  } : {
@@ -59111,23 +59817,18 @@ function deriveEventScheduleSummary(event) {
59111
59817
  occurrence
59112
59818
  };
59113
59819
  }
59114
- function derivePreviewRunSchedule(runSessions) {
59115
- const sessions = orderedLiveRunSessions(runSessions).filter(isValidOccurrence);
59820
+ function derivePreviewRunSchedule(event) {
59821
+ const sessions = orderedLiveRunSessions(event.run.sessions).filter(isValidOccurrence);
59116
59822
  const first = sessions[0];
59117
59823
  const last = sessions[sessions.length - 1];
59118
59824
  if (!first || !last) return { kind: "no-live-sessions" };
59119
- const recurrence = sessions.length > 1 ? deriveFixedRecurrence(sessions) : null;
59120
- const summary = sessions.length === 1 ? deriveEventScheduleSummary(first) : recurrence ? {
59121
- kind: "fixed-recurring-run",
59825
+ const authoredSummary = formatBookingSchedulePresentation("representative" in event ? event.representative : event);
59826
+ const summary = !authoredSummary && sessions.length === 1 ? deriveEventScheduleSummary(first) : {
59827
+ kind: "scheduled-run",
59122
59828
  first,
59123
59829
  last,
59124
59830
  sessionCount: sessions.length,
59125
- recurrenceLabel: recurrence
59126
- } : {
59127
- kind: "irregular-multi-session-run",
59128
- first,
59129
- last,
59130
- sessionCount: sessions.length
59831
+ authoredSummary
59131
59832
  };
59132
59833
  return {
59133
59834
  kind: "scheduled",
@@ -59143,62 +59844,9 @@ function occurrenceFromEvent(event) {
59143
59844
  timeZone: event.timeZone
59144
59845
  };
59145
59846
  }
59146
- function deriveFixedRecurrence(sessions) {
59147
- const [first, second] = sessions;
59148
- if (!first || !second) return null;
59149
- if (!sessions.every((session) => sameLocalTime(first, session))) return null;
59150
- const intervalDays = localDayNumber(second.startsAt, second.timeZone) - localDayNumber(first.startsAt, first.timeZone);
59151
- if (intervalDays <= 0) return null;
59152
- for (let index = 2; index < sessions.length; index += 1) {
59153
- const previous = sessions[index - 1];
59154
- const current = sessions[index];
59155
- if (!previous || !current) return null;
59156
- const currentInterval = localDayNumber(current.startsAt, current.timeZone) - localDayNumber(previous.startsAt, previous.timeZone);
59157
- if (currentInterval !== intervalDays) return null;
59158
- }
59159
- if (intervalDays === 1) return "Every day";
59160
- const weekday = localDateParts(first.startsAt, first.timeZone).weekday;
59161
- if (intervalDays === 7) return `Every ${weekday}`;
59162
- if (intervalDays % 7 === 0) {
59163
- return `Every ${intervalDays / 7} weeks on ${weekday}`;
59164
- }
59165
- return `Every ${intervalDays} days`;
59166
- }
59167
- function sameLocalTime(reference, candidate) {
59168
- const referenceStart = localDateParts(reference.startsAt, reference.timeZone);
59169
- const candidateStart = localDateParts(candidate.startsAt, candidate.timeZone);
59170
- const referenceEnd = localDateParts(reference.endsAt, reference.timeZone);
59171
- const candidateEnd = localDateParts(candidate.endsAt, candidate.timeZone);
59172
- return referenceStart.hour === candidateStart.hour && referenceStart.minute === candidateStart.minute && referenceEnd.hour === candidateEnd.hour && referenceEnd.minute === candidateEnd.minute;
59173
- }
59174
59847
  function isValidOccurrence(value) {
59175
59848
  return Number.isFinite(Date.parse(value.startsAt)) && Number.isFinite(Date.parse(value.endsAt));
59176
59849
  }
59177
- function localDayNumber(isoTimestamp, timeZone) {
59178
- const parts = localDateParts(isoTimestamp, timeZone);
59179
- return Math.floor(Date.UTC(parts.year, parts.month - 1, parts.day) / 864e5);
59180
- }
59181
- function localDateParts(isoTimestamp, timeZone) {
59182
- const parts = new Intl.DateTimeFormat("en-GB", {
59183
- timeZone,
59184
- year: "numeric",
59185
- month: "numeric",
59186
- day: "numeric",
59187
- hour: "2-digit",
59188
- minute: "2-digit",
59189
- hourCycle: "h23",
59190
- weekday: "long"
59191
- }).formatToParts(new Date(isoTimestamp));
59192
- const value = (type) => parts.find((part) => part.type === type)?.value ?? "";
59193
- return {
59194
- year: Number(value("year")),
59195
- month: Number(value("month")),
59196
- day: Number(value("day")),
59197
- hour: Number(value("hour")),
59198
- minute: Number(value("minute")),
59199
- weekday: value("weekday")
59200
- };
59201
- }
59202
59850
 
59203
59851
  //#endregion
59204
59852
  //#region ../blocks/src/system/runtime/nodes/events/shared/eventCardListContent.ts
@@ -59253,7 +59901,7 @@ function offeringLeadDate(event, startMs) {
59253
59901
  };
59254
59902
  }
59255
59903
  function offeringSchedule(event, now) {
59256
- const run = event.bookingScope === "whole_series" ? derivePreviewRunSchedule(event.run.sessions) : null;
59904
+ const run = event.bookingScope === "whole_series" ? derivePreviewRunSchedule(event) : null;
59257
59905
  if (run?.kind === "no-live-sessions") return {
59258
59906
  numeral: null,
59259
59907
  schedule: null
@@ -59284,7 +59932,7 @@ function scheduleLine(summary, event, now) {
59284
59932
  date: "short",
59285
59933
  year: now ? year(value) : "omit"
59286
59934
  });
59287
- const dates = (startsAt, endsAt) => formatDateRange({
59935
+ const dates = (startsAt, endsAt, order = "chronological") => formatDateRange({
59288
59936
  kind: "civil",
59289
59937
  value: getLocalScheduleDate(new Date(startsAt), context.timeZone)
59290
59938
  }, {
@@ -59292,6 +59940,7 @@ function scheduleLine(summary, event, now) {
59292
59940
  value: getLocalScheduleDate(new Date(endsAt), resolveBookingBusinessTimeZone(last.timeZone))
59293
59941
  }, context, {
59294
59942
  date: "short",
59943
+ order,
59295
59944
  year: now && year(startsAt) === "omit" && year(endsAt, resolveBookingBusinessTimeZone(last.timeZone)) === "omit" ? "omit" : now ? "numeric" : "omit"
59296
59945
  });
59297
59946
  switch (summary.kind) {
@@ -59305,8 +59954,7 @@ function scheduleLine(summary, event, now) {
59305
59954
  zone
59306
59955
  }) : date(summary.occurrence.startsAt);
59307
59956
  case "multi-day-span": return dates(summary.occurrence.startsAt, summary.occurrence.endsAt);
59308
- case "fixed-recurring-run": return `${dates(summary.first.startsAt, summary.last.startsAt)} · ${summary.recurrenceLabel}`;
59309
- case "irregular-multi-session-run": return `${dates(summary.first.startsAt, summary.last.startsAt)} · ${summary.sessionCount} dates`;
59957
+ case "scheduled-run": return `${dates(summary.first.startsAt, summary.last.startsAt, "session")} · ${summary.authoredSummary?.text ?? `${summary.sessionCount} dates`}`;
59310
59958
  }
59311
59959
  }
59312
59960
  function offeringAvailability(event) {
@@ -59945,16 +60593,44 @@ var event_details_server_default = EventDetailsSSR;
59945
60593
 
59946
60594
  //#endregion
59947
60595
  //#region ../blocks/src/system/runtime/nodes/location-map.view.tsx
59948
- function LocationMapView({ addressText, location, zoom, heightPx, showAddressText, showDirections, directionsLabel, unresolvedMessage, renderMap }) {
59949
- const directionsUrl = buildGoogleMapsDirectionsUrl({
60596
+ function OpeningHoursTable({ hours: hours$1 }) {
60597
+ if (!hours$1) return null;
60598
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("caption", { children: "Opening hours" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: formatOpeningHoursRows(hours$1).map((row$1) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", {
60599
+ "data-closed": row$1.kind === "closed" ? true : undefined,
60600
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
60601
+ scope: "row",
60602
+ children: row$1.label
60603
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: row$1.hours })]
60604
+ }, row$1.label)) })] }), hours$1.note ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: hours$1.note }) : null] });
60605
+ }
60606
+ function LocationMapDirections({ url, label }) {
60607
+ return url ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemedButtonLink, {
60608
+ href: url,
60609
+ target: "_blank",
60610
+ rel: "noreferrer",
60611
+ variant: "ghost",
60612
+ size: "sm",
60613
+ extraClassName: "rb-w-fit",
60614
+ label
60615
+ }) : null;
60616
+ }
60617
+ function LocationMapView(props$1) {
60618
+ const { addressText, location, renderMap, zoom, heightPx, showAddressText, directionsLabel } = props$1;
60619
+ const directionsUrl = props$1.showDirections ? buildGoogleMapsDirectionsUrl({
59950
60620
  addressText,
59951
60621
  location
59952
- });
59953
- if (!location && !addressText && !directionsUrl) {
60622
+ }) : null;
60623
+ const hours$1 = props$1.showOpeningHours === false ? null : props$1.openingHours ?? null;
60624
+ if (![
60625
+ location,
60626
+ addressText,
60627
+ directionsUrl,
60628
+ hours$1
60629
+ ].some(Boolean)) {
59954
60630
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
59955
60631
  className: "rb-caption",
59956
60632
  style: textColorStyle("mutedText"),
59957
- children: unresolvedMessage ?? "Location details coming soon."
60633
+ children: props$1.unresolvedMessage ?? "Location details coming soon."
59958
60634
  });
59959
60635
  }
59960
60636
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -59972,15 +60648,11 @@ function LocationMapView({ addressText, location, zoom, heightPx, showAddressTex
59972
60648
  style: textColorStyle("mutedText"),
59973
60649
  children: addressText
59974
60650
  }) : null,
59975
- showDirections && directionsUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemedButtonLink, {
59976
- href: directionsUrl,
59977
- target: "_blank",
59978
- rel: "noreferrer",
59979
- variant: "ghost",
59980
- size: "sm",
59981
- extraClassName: "rb-w-fit",
60651
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OpeningHoursTable, { hours: hours$1 }),
60652
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LocationMapDirections, {
60653
+ url: directionsUrl,
59982
60654
  label: directionsLabel
59983
- }) : null
60655
+ })
59984
60656
  ]
59985
60657
  });
59986
60658
  }
@@ -59997,14 +60669,7 @@ function LocationMapSSR(props$1) {
59997
60669
  children: [
59998
60670
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IslandPropsScript, { value: hydrationProps }),
59999
60671
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LocationMapView, {
60000
- addressText: normalized.addressText,
60001
- location: normalized.location,
60002
- zoom: normalized.zoom,
60003
- heightPx: normalized.heightPx,
60004
- showAddressText: normalized.showAddressText,
60005
- showDirections: normalized.showDirections,
60006
- directionsLabel: normalized.directionsLabel,
60007
- unresolvedMessage: normalized.unresolvedMessage,
60672
+ ...normalized,
60008
60673
  renderMap: ({ addressText, className, height }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
60009
60674
  className,
60010
60675
  style: {
@@ -60646,7 +61311,7 @@ function FeaturedOfferingSummary({ item }) {
60646
61311
  children: [
60647
61312
  details.recurrence ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Repeats" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: details.recurrence })] }) : null,
60648
61313
  details.sessionCount ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Sessions" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: details.sessionCount })] }) : null,
60649
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Time" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: details.timeRange })] }),
61314
+ details.timeRange ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Time" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: details.timeRange })] }) : null,
60650
61315
  details.venue ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Venue" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: details.venue })] }) : null
60651
61316
  ]
60652
61317
  })]
@@ -60660,7 +61325,7 @@ function featuredSummaryDetails(item) {
60660
61325
  deliveryMode: item.event.deliveryMode
60661
61326
  });
60662
61327
  case "run": {
60663
- const schedule = derivePreviewRunSchedule(item.run.run.sessions);
61328
+ const schedule = derivePreviewRunSchedule(item.run);
60664
61329
  return schedule.kind === "scheduled" ? formatFeaturedSummary(schedule.summary, {
60665
61330
  venue: item.run.representative.venue?.name ?? null,
60666
61331
  siteTimeZone: item.run.representative.siteTimeZone,
@@ -60674,33 +61339,33 @@ function formatFeaturedSummary(summary, place) {
60674
61339
  switch (summary.kind) {
60675
61340
  case "single-occurrence": return detailsFromOccurrences({
60676
61341
  first: summary.occurrence,
60677
- last: null,
61342
+ end: null,
60678
61343
  place
60679
61344
  });
60680
61345
  case "multi-day-span": return detailsFromOccurrences({
60681
61346
  first: summary.occurrence,
60682
- last: summary.occurrence,
60683
- place,
60684
- spanUsesEndDate: true
60685
- });
60686
- case "fixed-recurring-run": return detailsFromOccurrences({
60687
- first: summary.first,
60688
- last: summary.last,
60689
- place,
60690
- recurrence: summary.recurrenceLabel
61347
+ end: {
61348
+ dateTime: summary.occurrence.endsAt,
61349
+ timeZone: summary.occurrence.timeZone
61350
+ },
61351
+ place
60691
61352
  });
60692
- case "irregular-multi-session-run": return detailsFromOccurrences({
61353
+ case "scheduled-run": return detailsFromOccurrences({
60693
61354
  first: summary.first,
60694
- last: summary.last,
61355
+ end: {
61356
+ dateTime: summary.last.startsAt,
61357
+ timeZone: summary.last.timeZone
61358
+ },
60695
61359
  place,
60696
- sessionCount: `${summary.sessionCount} dates`
61360
+ recurrence: summary.authoredSummary ?? undefined,
61361
+ sessionCount: `${summary.sessionCount} date${summary.sessionCount === 1 ? "" : "s"}`
60697
61362
  });
60698
61363
  default: return assertNever(summary, "Unhandled offerings preview schedule summary");
60699
61364
  }
60700
61365
  }
60701
61366
  function detailsFromOccurrences(input) {
60702
- const { first, last, place } = input;
60703
- const endDateTime = input.spanUsesEndDate ? first.endsAt : last?.startsAt ?? first.startsAt;
61367
+ const { first, end, place } = input;
61368
+ const endDateTime = end?.dateTime ?? first.startsAt;
60704
61369
  const options = eventDateOptions({
60705
61370
  timeZone: first.timeZone,
60706
61371
  siteTimeZone: place.siteTimeZone,
@@ -60710,13 +61375,15 @@ function detailsFromOccurrences(input) {
60710
61375
  timeZone,
60711
61376
  locale: options.locale
60712
61377
  });
61378
+ const startDate = summaryDate(first.startsAt, first.timeZone);
61379
+ const endDate = end ? summaryDate(end.dateTime, end.timeZone) : null;
60713
61380
  return {
60714
- startDate: summaryDate(first.startsAt, first.timeZone),
61381
+ startDate,
60715
61382
  startDateTime: first.startsAt,
60716
- endDate: last ? summaryDate(endDateTime, last.timeZone) : null,
61383
+ endDate: endDate === startDate ? null : endDate,
60717
61384
  endDateTime,
60718
- timeRange: formatEventTimeRange(first, options),
60719
- recurrence: input.recurrence ?? null,
61385
+ timeRange: input.recurrence?.clock ? null : formatEventTimeRange(first, options),
61386
+ recurrence: input.recurrence?.text ?? null,
60720
61387
  sessionCount: input.sessionCount ?? null,
60721
61388
  venue: place.venue
60722
61389
  };
@@ -60766,8 +61433,13 @@ function renderSingleOfferingCard(item, options) {
60766
61433
  }, item.run.eventSeriesId);
60767
61434
  }
60768
61435
  }
61436
+ /** The authored clock replaces live time only when it was successfully formatted. */
61437
+ function formatRunScheduleTime(schedule, options) {
61438
+ const authored = schedule.summary.kind === "scheduled-run" ? schedule.summary.authoredSummary : null;
61439
+ return [authored?.text, authored?.clock ? null : formatEventTimeRange(schedule.firstSession, options)].filter(Boolean).join(" · ");
61440
+ }
60769
61441
  function WholeSeriesRunCompactRow({ group, path: path$3, now }) {
60770
- const schedule = derivePreviewRunSchedule(group.run.sessions);
61442
+ const schedule = derivePreviewRunSchedule(group);
60771
61443
  if (schedule.kind === "no-live-sessions") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunWithoutLiveDates, {
60772
61444
  group,
60773
61445
  path: path$3
@@ -60779,7 +61451,7 @@ function WholeSeriesRunCompactRow({ group, path: path$3, now }) {
60779
61451
  deliveryMode: group.representative.deliveryMode
60780
61452
  });
60781
61453
  const dateRail = formatEventCompactDateRail(firstSession.startsAt, options);
60782
- const timeRange = formatEventTimeRange(firstSession, options);
61454
+ const timeRange = formatRunScheduleTime(schedule, options);
60783
61455
  const price$1 = formatEventPriceDisplay(group.representative.pricing);
60784
61456
  const statusLabel = resolveWholeSeriesBookingPhase(group.run, now).kind === "open" ? null : "Registration closed";
60785
61457
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("article", {
@@ -60853,7 +61525,7 @@ function WholeSeriesRunCompactRow({ group, path: path$3, now }) {
60853
61525
  });
60854
61526
  }
60855
61527
  function WholeSeriesRunFeatureCard({ group, now, path: path$3, buttonVariant, buttonText }) {
60856
- const schedule = derivePreviewRunSchedule(group.run.sessions);
61528
+ const schedule = derivePreviewRunSchedule(group);
60857
61529
  if (schedule.kind === "no-live-sessions") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunWithoutLiveDates, {
60858
61530
  group,
60859
61531
  path: path$3
@@ -60865,7 +61537,7 @@ function WholeSeriesRunFeatureCard({ group, now, path: path$3, buttonVariant, bu
60865
61537
  deliveryMode: group.representative.deliveryMode
60866
61538
  });
60867
61539
  const formattedDate = formatEventCompactDateRail(firstSession.startsAt, options).label;
60868
- const timeRange = formatEventTimeRange(firstSession, options);
61540
+ const timeRange = formatRunScheduleTime(schedule, options);
60869
61541
  const price$1 = formatEventPriceDisplay(group.representative.pricing);
60870
61542
  const statusLabel = resolveWholeSeriesBookingPhase(group.run, now).kind === "open" ? null : "Registration closed";
60871
61543
  const description = group.representative.description;
@@ -61197,7 +61869,7 @@ function bookingActionContent(input) {
61197
61869
  default: return assertNever(input.booking);
61198
61870
  }
61199
61871
  }
61200
- function RecurringSchedule({ schedule, buttonText = "Book", buttonVariant = "primary", showVenue = true, emptyMessage = "Regular schedule details will be added shortly." }) {
61872
+ function RecurringSchedule({ dateContext = resolveDateRuntimeContext(undefined), schedule, buttonText = "Book", buttonVariant = "primary", showVenue = true, emptyMessage = "Regular schedule details will be added shortly." }) {
61201
61873
  const projection = schedule ?? {
61202
61874
  items: [],
61203
61875
  diagnostics: []
@@ -61209,8 +61881,28 @@ function RecurringSchedule({ schedule, buttonText = "Book", buttonVariant = "pri
61209
61881
  children: emptyMessage
61210
61882
  });
61211
61883
  }
61212
- const renderRow = (item) => {
61213
- const rowContent = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
61884
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
61885
+ className: "recurring-schedule",
61886
+ "data-diagnostic-count": projection.diagnostics.length,
61887
+ role: "list",
61888
+ children: projection.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RecurringScheduleRow, {
61889
+ item,
61890
+ locale: dateContext.locale,
61891
+ buttonText: buttonText ?? "Book",
61892
+ buttonVariant: buttonVariant ?? "primary",
61893
+ showVenue: showVenue ?? true
61894
+ }, item.seriesId))
61895
+ });
61896
+ }
61897
+ function RecurringScheduleRow({ item, locale, buttonText, buttonVariant, showVenue }) {
61898
+ const startLabel = formatEventClockLabel(item.cadence.startTime, locale);
61899
+ const endLabel = formatEventClockLabel(item.cadence.endTime, locale);
61900
+ const description = `${item.title}, ${item.cadence.weekday} at ${startLabel}`;
61901
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("article", {
61902
+ className: "recurring-schedule-row",
61903
+ role: "listitem",
61904
+ "aria-label": description,
61905
+ children: [
61214
61906
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
61215
61907
  className: "recurring-schedule-day",
61216
61908
  children: item.cadence.weekday
@@ -61220,7 +61912,7 @@ function RecurringSchedule({ schedule, buttonText = "Book", buttonVariant = "pri
61220
61912
  children: [
61221
61913
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("time", {
61222
61914
  dateTime: item.cadence.startTime,
61223
- children: item.cadence.startTime
61915
+ children: startLabel
61224
61916
  }),
61225
61917
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
61226
61918
  "aria-hidden": "true",
@@ -61228,7 +61920,7 @@ function RecurringSchedule({ schedule, buttonText = "Book", buttonVariant = "pri
61228
61920
  }),
61229
61921
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("time", {
61230
61922
  dateTime: item.cadence.endTime,
61231
- children: item.cadence.endTime
61923
+ children: endLabel
61232
61924
  })
61233
61925
  ]
61234
61926
  }),
@@ -61246,24 +61938,12 @@ function RecurringSchedule({ schedule, buttonText = "Book", buttonVariant = "pri
61246
61938
  className: "recurring-schedule-action",
61247
61939
  children: bookingActionContent({
61248
61940
  booking: item.booking,
61249
- buttonVariant: buttonVariant ?? "primary",
61250
- buttonText: buttonText ?? "Book",
61251
- accessibleName: `${buttonText ?? "Book"} ${item.title}, ${item.cadence.weekday} at ${item.cadence.startTime}`
61941
+ buttonVariant,
61942
+ buttonText,
61943
+ accessibleName: `${buttonText} ${description}`
61252
61944
  })
61253
61945
  })
61254
- ] });
61255
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("article", {
61256
- className: "recurring-schedule-row",
61257
- role: "listitem",
61258
- "aria-label": `${item.title}, ${item.cadence.weekday} at ${item.cadence.startTime}`,
61259
- children: rowContent
61260
- });
61261
- };
61262
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
61263
- className: "recurring-schedule",
61264
- "data-diagnostic-count": projection.diagnostics.length,
61265
- role: "list",
61266
- children: projection.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Fragment, { children: renderRow(item) }, item.seriesId))
61946
+ ]
61267
61947
  });
61268
61948
  }
61269
61949
 
@@ -67189,7 +67869,8 @@ function arrangementContentFacts(content) {
67189
67869
  photo: photoCount > 0,
67190
67870
  facts: Array.isArray(content.facts) && content.facts.length > 0,
67191
67871
  featured,
67192
- outcome: typeof content.outcome === "string" && content.outcome.length > 0
67872
+ outcome: typeof content.outcome === "string" && content.outcome.length > 0,
67873
+ figure: typeof content.figure === "string" && content.figure.trim().length > 0
67193
67874
  }
67194
67875
  };
67195
67876
  }
@@ -67349,7 +68030,7 @@ function resolveArrangementRoleSurfaces(look, runtime, background) {
67349
68030
  }
67350
68031
  /** The arrangement key is resolved once by planBlock, including pinned Looks. */
67351
68032
  function heroPhotoGround(entry) {
67352
- return entry.kind === "renderable" && entry.block.kind === "block.hero" ? { paintsGround: entry.effectiveArrangementKey === "classic" } : undefined;
68033
+ return entry.kind === "renderable" && entry.block.kind === "block.hero" ? { paintsGround: heroPhotoPaintsGround(entry.effectiveArrangementKey ?? undefined) } : undefined;
67353
68034
  }
67354
68035
  /** Header consumers receive the opening that the public render plan actually admits. */
67355
68036
  function resolvePageOpeningSection(page, options = {}) {
@@ -68470,7 +69151,7 @@ function compilePagePushCommands(input) {
68470
69151
  });
68471
69152
  }
68472
69153
  function pageCommandRouting(diff) {
68473
- const changedPages = diff.pages.filter((pageDiff) => pageDiff.type !== "unchanged" || Boolean(pageDiff.statusChange));
69154
+ const changedPages = diff.pages.filter(hasPageOperations);
68474
69155
  if (changedPages.length === 0) return { kind: "no_page_changes" };
68475
69156
  return { kind: "command_backed" };
68476
69157
  }
@@ -68562,7 +69243,7 @@ function pageCommandReportToSyncResult(report) {
68562
69243
  });
68563
69244
  }
68564
69245
  function pageCommandsForDiff(input) {
68565
- if (input.pageDiff.type === "unchanged" && !input.pageDiff.statusChange) {
69246
+ if (!hasPageOperations(input.pageDiff)) {
68566
69247
  return ok(emptyPageCommandCompileFragment());
68567
69248
  }
68568
69249
  if (!input.localPage) {
@@ -68585,10 +69266,7 @@ function pageCommandsForDiff(input) {
68585
69266
  input: {
68586
69267
  identifier: localPageIdentifier,
68587
69268
  slug: localPageIdentifier,
68588
- title: input.localPage.title,
68589
- path: input.localPage.path,
68590
- ...input.localPage.seoTitle ? { seoTitle: input.localPage.seoTitle } : {},
68591
- ...input.localPage.seoDescription ? { seoDescription: input.localPage.seoDescription } : {},
69269
+ ...pageMetadataForUpsert(input.localPage),
68592
69270
  ...input.localPage.status ? { status: input.localPage.status } : {}
68593
69271
  }
68594
69272
  };
@@ -78049,7 +78727,7 @@ async function loadConfig(configPath, output) {
78049
78727
  return { errorMessage: "No config path provided." };
78050
78728
  }
78051
78729
  try {
78052
- const { loadConfigFile: loadConfigFile$1 } = await import("../load-config-Kg45qY-N.mjs");
78730
+ const { loadConfigFile: loadConfigFile$1 } = await import("../load-config-JWz__Dkx.mjs");
78053
78731
  const rawConfig = await loadConfigFile$1(configPath);
78054
78732
  const parsed = riverbankSiteConfigSchema.safeParse(rawConfig);
78055
78733
  if (!parsed.success) {
@@ -80616,15 +81294,13 @@ function occurrenceCapacityFromOptions(options) {
80616
81294
  return options.inheritCapacity ? { kind: "inherit" } : undefined;
80617
81295
  }
80618
81296
  function formatOccurrenceLocal(iso, timeZone) {
80619
- return new Intl.DateTimeFormat("en-GB", {
80620
- timeZone,
80621
- year: "numeric",
80622
- month: "2-digit",
80623
- day: "2-digit",
80624
- hour: "2-digit",
80625
- minute: "2-digit",
80626
- hourCycle: "h23"
80627
- }).format(new Date(iso));
81297
+ return formatDateTime(toUtcIsoDateTimeString(iso), {
81298
+ locale: "en-GB",
81299
+ timeZone: resolveBookingTimeZoneId(timeZone)
81300
+ }, {
81301
+ date: "short",
81302
+ zone: "omit"
81303
+ });
80628
81304
  }
80629
81305
  function formatDeletionEligibility(occurrence) {
80630
81306
  switch (occurrence.deletionEligibility.kind) {