riverbankcms 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { A as parseFooterCompositionV1, Ht as ok, K as parseUtcIsoDateTimeString, M as isCanonicalRichTextDoc, P as isPresent, Vt as err, X as parseBookingTimeZone, b as stableJsonStringify, k as footerNavigationLayouts, l as EVENT_PRESET_VALUES, lt as assertNever, s as resolveSiteVerticalBookingConfig, t as NAVIGATION_LOCATIONS, u as ORDINARY_EVENT_PRESET_VALUES } from "./src-B8fOSKJ6.mjs";
2
+ import { A as parseFooterCompositionV1, Ht as err, M as isCanonicalRichTextDoc, P as isPresent, Ut as ok, Z as parseBookingTimeZone, b as stableJsonStringify, k as footerNavigationLayouts, l as EVENT_PRESET_VALUES, n as NAVIGATION_LOCATIONS, q as parseUtcIsoDateTimeString, s as resolveSiteVerticalBookingConfig, u as ORDINARY_EVENT_PRESET_VALUES, ut as assertNever } from "./src-DIAVNRwo.mjs";
3
3
  import "node:module";
4
4
  import { z } from "zod";
5
5
 
@@ -61,41 +61,6 @@ function siteCommandResourceResult(ref, resourceId, effect) {
61
61
  };
62
62
  }
63
63
 
64
- //#endregion
65
- //#region ../site-commands/src/capabilityGaps.ts
66
- const staticExecutionGapCodes = [
67
- "event_occurrence_required",
68
- "event_occurrence_invalid",
69
- "event_occurrence_venue_mapping_unsupported",
70
- "event_booking_offer_invalid",
71
- "event_ticket_single_occurrence_scope_unsupported",
72
- "event_manual_payment_unsupported",
73
- "event_external_payment_unsupported",
74
- "event_waitlist_requires_waitlist_adapter",
75
- "event_media_content_requires_media_adapter",
76
- "event_linked_content_requires_content_adapter",
77
- "event_delivery_mode_requires_booking_surface_adapter",
78
- "event_whole_series_page_fields_unsupported",
79
- "event_whole_series_passes_unsupported",
80
- "event_whole_series_pricing_unsupported",
81
- "event_whole_series_schedule_update_unsupported",
82
- "event_whole_series_staff_update_unsupported"
83
- ];
84
- const plannerCapabilityGapCodes = ["command_metadata_support_gap"];
85
- const adapterCapabilityGapCodes = [
86
- "demo_recipe_adapter_missing",
87
- "media_manifest_storage_resolution_missing",
88
- "navigation_content_entry_refs_unsupported",
89
- "staff_site_user_requires_dashboard_user",
90
- "team_member_profile_requires_dashboard_user",
91
- "generated_navigation_command_unsupported"
92
- ];
93
- const siteCommandCapabilityGapCodes = [
94
- ...staticExecutionGapCodes,
95
- ...plannerCapabilityGapCodes,
96
- ...adapterCapabilityGapCodes
97
- ];
98
-
99
64
  //#endregion
100
65
  //#region ../site-commands/src/refs.ts
101
66
  const resourceKinds = [
@@ -123,6 +88,7 @@ const resourceKinds = [
123
88
  "pass-redemption",
124
89
  "order",
125
90
  "appointment-service",
91
+ "appointment-service-category",
126
92
  "appointment-resource",
127
93
  "availability"
128
94
  ];
@@ -167,6 +133,9 @@ function createVenueRef(parts) {
167
133
  function createEventCategoryRef(parts) {
168
134
  return `event-category:${joinRefSlugParts(parts)}`;
169
135
  }
136
+ function createAppointmentServiceCategoryRef(parts) {
137
+ return `appointment-service-category:${joinRefSlugParts(parts)}`;
138
+ }
170
139
  function createEventSeriesRef(parts) {
171
140
  return `event:${joinRefSlugParts(parts)}`;
172
141
  }
@@ -245,6 +214,118 @@ function isValidIanaTimezone(value) {
245
214
  }
246
215
  }
247
216
 
217
+ //#endregion
218
+ //#region ../site-commands/src/appointmentServiceCategoryCommands.ts
219
+ const appointmentServiceCategoryRefSchema = resourceRefSchema("appointment-service-category");
220
+ /**
221
+ * Creates or updates one appointment-service category (issue 1403).
222
+ *
223
+ * The category's `identifier` is its stable key: it never changes after the
224
+ * category is created, so a rename in the dashboard doesn't break a rerun.
225
+ * When a command leaves it out, the identifier is the ref's slug, which keeps
226
+ * reruns of the same command pointed at the same category.
227
+ *
228
+ * `description` and `sortOrder` describe the wanted state. An omitted
229
+ * description means no description. An omitted `sortOrder` puts a new
230
+ * category last and leaves an existing one where it is.
231
+ */
232
+ const upsertAppointmentServiceCategoryCommandSchema = z.object({
233
+ type: z.literal("upsertAppointmentServiceCategory"),
234
+ ref: appointmentServiceCategoryRefSchema,
235
+ input: z.object({
236
+ identifier: recipeIdentifierSchema.optional(),
237
+ name: z.string().trim().min(1),
238
+ description: z.string().trim().nullable().optional(),
239
+ sortOrder: nonNegativeIntegerSchema.optional()
240
+ }).strict()
241
+ }).strict();
242
+ /**
243
+ * The three ways a service can be placed among the categories. Each variant
244
+ * forbids the other key, so a command naming both a `categoryRef` and a
245
+ * `categoryIdentifier` can't be represented.
246
+ */
247
+ const noAppointmentServiceCategoryFields = {
248
+ categoryRef: z.undefined().optional(),
249
+ categoryIdentifier: z.undefined().optional()
250
+ };
251
+ const appointmentServiceCategoryRefFields = {
252
+ categoryRef: appointmentServiceCategoryRefSchema,
253
+ categoryIdentifier: z.undefined().optional()
254
+ };
255
+ const appointmentServiceCategoryIdentifierFields = {
256
+ categoryRef: z.undefined().optional(),
257
+ categoryIdentifier: recipeIdentifierSchema
258
+ };
259
+ /**
260
+ * Rejects a command that names both category keys with one clear message,
261
+ * before the union reports that no variant matched.
262
+ */
263
+ function rejectBothAppointmentServiceCategoryKeys(value, ctx) {
264
+ if (typeof value !== "object" || value === null || !("categoryRef" in value) || !("categoryIdentifier" in value) || value.categoryRef === undefined || value.categoryIdentifier === undefined) {
265
+ return;
266
+ }
267
+ ctx.addIssue({
268
+ code: "custom",
269
+ path: ["categoryIdentifier"],
270
+ message: "Use categoryRef for a category upserted in this batch, or categoryIdentifier for one that already exists, not both."
271
+ });
272
+ }
273
+ /** Where an `upsertAppointmentService` command puts the service. */
274
+ function appointmentServiceCategoryPlacement(input) {
275
+ if (input.categoryRef !== undefined) {
276
+ return {
277
+ kind: "batch_ref",
278
+ categoryRef: input.categoryRef
279
+ };
280
+ }
281
+ if (input.categoryIdentifier !== undefined) {
282
+ return {
283
+ kind: "identifier",
284
+ identifier: input.categoryIdentifier
285
+ };
286
+ }
287
+ return { kind: "uncategorized" };
288
+ }
289
+ /** The stable key an `upsertAppointmentServiceCategory` command writes to. */
290
+ function appointmentServiceCategoryIdentifierForCommand(command) {
291
+ return command.input.identifier ?? refSlug(command.ref);
292
+ }
293
+
294
+ //#endregion
295
+ //#region ../site-commands/src/capabilityGaps.ts
296
+ const staticExecutionGapCodes = [
297
+ "event_occurrence_required",
298
+ "event_occurrence_invalid",
299
+ "event_occurrence_venue_mapping_unsupported",
300
+ "event_booking_offer_invalid",
301
+ "event_ticket_single_occurrence_scope_unsupported",
302
+ "event_manual_payment_unsupported",
303
+ "event_external_payment_unsupported",
304
+ "event_waitlist_requires_waitlist_adapter",
305
+ "event_media_content_requires_media_adapter",
306
+ "event_linked_content_requires_content_adapter",
307
+ "event_delivery_mode_requires_booking_surface_adapter",
308
+ "event_whole_series_page_fields_unsupported",
309
+ "event_whole_series_passes_unsupported",
310
+ "event_whole_series_pricing_unsupported",
311
+ "event_whole_series_schedule_update_unsupported",
312
+ "event_whole_series_staff_update_unsupported"
313
+ ];
314
+ const plannerCapabilityGapCodes = ["command_metadata_support_gap"];
315
+ const adapterCapabilityGapCodes = [
316
+ "demo_recipe_adapter_missing",
317
+ "media_manifest_storage_resolution_missing",
318
+ "navigation_content_entry_refs_unsupported",
319
+ "staff_site_user_requires_dashboard_user",
320
+ "team_member_profile_requires_dashboard_user",
321
+ "generated_navigation_command_unsupported"
322
+ ];
323
+ const siteCommandCapabilityGapCodes = [
324
+ ...staticExecutionGapCodes,
325
+ ...plannerCapabilityGapCodes,
326
+ ...adapterCapabilityGapCodes
327
+ ];
328
+
248
329
  //#endregion
249
330
  //#region ../site-commands/src/bookingCommandValues.ts
250
331
  const bookingPassProductUseCases = ["event_pass", "appointment_package"];
@@ -2449,8 +2530,8 @@ const BACKGROUND_ROLE_NAMES = new Set([
2449
2530
  "muted",
2450
2531
  "neutral"
2451
2532
  ]);
2452
- function onColorTokenName(backgroundToken) {
2453
- return `on${backgroundToken.split("-").filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("")}`;
2533
+ function onColorTokenName(backgroundToken$1) {
2534
+ return `on${backgroundToken$1.split("-").filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("")}`;
2454
2535
  }
2455
2536
  function isOnColorBackgroundCandidateToken(token) {
2456
2537
  const role = token.match(/^(.*?)(?:-\d{2,3})?$/)?.[1] ?? token;
@@ -2584,8 +2665,8 @@ function resolveDeterministicForeground(backgroundReference, candidates, palette
2584
2665
  if (!backgroundHex) {
2585
2666
  return resolveTrustedTreatmentForeground(candidates, palette);
2586
2667
  }
2587
- const backgroundToken = paletteToken(backgroundReference);
2588
- const derivedOnToken = backgroundToken ? onColorTokenName(backgroundToken) : null;
2668
+ const backgroundToken$1 = paletteToken(backgroundReference);
2669
+ const derivedOnToken = backgroundToken$1 ? onColorTokenName(backgroundToken$1) : null;
2589
2670
  const text = firstSafeToken([
2590
2671
  candidates.text,
2591
2672
  derivedOnToken,
@@ -2658,6 +2739,37 @@ function normalizedReference(value) {
2658
2739
  return normalized.length > 0 ? normalized : null;
2659
2740
  }
2660
2741
 
2742
+ //#endregion
2743
+ //#region ../theme-core/src/surfaces/logoVariant.ts
2744
+ /** Concrete hex behind a plain, opaque palette token, or null otherwise. */
2745
+ function opaquePaletteHexForToken(expandedPalette, token) {
2746
+ if (!token) return null;
2747
+ const parts = parseColorTokenParts(token);
2748
+ if (parts.kind !== "token" || parts.opacityPercent !== null) return null;
2749
+ return expandedPalette[parts.token] ?? null;
2750
+ }
2751
+ /**
2752
+ * Tone of the surface a foreground token was resolved for.
2753
+ *
2754
+ * A token with opacity has no known underlay to composite against, and a raw
2755
+ * or unrecognised token has no palette hex, so both are `unknown` rather than
2756
+ * a guess.
2757
+ */
2758
+ function surfaceToneFromForegroundToken(expandedPalette, foregroundToken) {
2759
+ const hex = opaquePaletteHexForToken(expandedPalette, foregroundToken);
2760
+ if (!hex) return "unknown";
2761
+ return featuresFromHex(hex).Y >= .5 ? "dark" : "light";
2762
+ }
2763
+ /**
2764
+ * The logo to draw on a surface of the given tone. A missing variant falls
2765
+ * back to the other one, and an unknown tone uses the light-backgrounds logo,
2766
+ * which is what every site rendered before variants existed.
2767
+ */
2768
+ function selectLogoVariant(tone, variants) {
2769
+ const { forLightBackgrounds, forDarkBackgrounds } = variants;
2770
+ return tone === "dark" ? forDarkBackgrounds ?? forLightBackgrounds : forLightBackgrounds ?? forDarkBackgrounds;
2771
+ }
2772
+
2661
2773
  //#endregion
2662
2774
  //#region ../theme-core/src/utils/colorStyles.ts
2663
2775
  const COLOR_VAR_PREFIX = "--tb-";
@@ -2733,18 +2845,30 @@ function backgroundColorStyle(token) {
2733
2845
  return { backgroundColor: rgbColorValue(token) };
2734
2846
  }
2735
2847
  /**
2736
- * Creates a text color style object from a theme token.
2848
+ * Converts a text color token to a CSS color value that follows the enclosing
2849
+ * surface. The semantic text tokens (`text`, `heading`, `mutedText`) resolve
2850
+ * to the surface foreground variables, so text adapts when a section's
2851
+ * background changes; any other token is a fixed theme color.
2737
2852
  *
2738
2853
  * @param token - Theme color token
2739
- * @returns CSSProperties with color
2854
+ * @returns CSS color value
2740
2855
  */
2741
- function textColorStyle(token) {
2856
+ function textColorValue(token) {
2742
2857
  const parsed = parseToken(token);
2743
2858
  const surfaceRole = parsed.raw ? null : surfaceRoleForTextToken(parsed.token);
2744
- if (surfaceRole) {
2745
- return { color: parsed.opacityPercent === undefined ? surfaceForegroundValue(surfaceRole) : surfaceForegroundValueWithOpacity(surfaceRole, parsed.opacityPercent) };
2859
+ if (!surfaceRole) {
2860
+ return rgbColorValue(token);
2746
2861
  }
2747
- return { color: rgbColorValue(token) };
2862
+ return parsed.opacityPercent === undefined ? surfaceForegroundValue(surfaceRole) : surfaceForegroundValueWithOpacity(surfaceRole, parsed.opacityPercent);
2863
+ }
2864
+ /**
2865
+ * Creates a text color style object from a theme token.
2866
+ *
2867
+ * @param token - Theme color token
2868
+ * @returns CSSProperties with color
2869
+ */
2870
+ function textColorStyle(token) {
2871
+ return { color: textColorValue(token) };
2748
2872
  }
2749
2873
  /**
2750
2874
  * Creates a border color style object from a theme token.
@@ -4573,16 +4697,18 @@ const themeV2RuntimeStyleFieldNames = [
4573
4697
  "_frameStyles",
4574
4698
  "_sectionStyles",
4575
4699
  "_containerStyles",
4576
- "_cardStyles",
4577
- "_transitionStyles",
4578
- "styleGroup"
4700
+ "_cardStyles"
4579
4701
  ];
4580
4702
  const themeV2BlockSpecificRawStyleFieldNames = [
4581
4703
  "_contentCardStyles",
4582
4704
  "_heroLayoutStyles",
4583
4705
  "_textCardStyles"
4584
4706
  ];
4585
- const themeV2NormalEditorRawStyleFieldNames = [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
4707
+ const themeV2NormalEditorRawStyleFieldNames = [
4708
+ ...themeV2RuntimeStyleFieldNames,
4709
+ ...themeV2BlockSpecificRawStyleFieldNames,
4710
+ "styleGroup"
4711
+ ];
4586
4712
  const themeV2NormalEditorRawStyleFieldNameSet = new Set(themeV2NormalEditorRawStyleFieldNames);
4587
4713
  const themeV2CompilerOwnedFieldNames = [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
4588
4714
  const pageDesignEditorBackgroundOverrideOwner = "page-design-editor";
@@ -4594,6 +4720,21 @@ function isThemeV2NormalEditorRawStylePath(fieldPath) {
4594
4720
  const rootField = fieldPath.split(".")[0] ?? "";
4595
4721
  return isThemeV2NormalEditorRawStyleFieldName(rootField);
4596
4722
  }
4723
+ const staleTransitionStyleGroupPrefix = "site-style-transition-";
4724
+ /**
4725
+ * A `styleGroup` in the retired `site-style-transition-*` namespace is residue
4726
+ * from the old per-block transition path (issue 1422). Any other value is
4727
+ * authored and must survive.
4728
+ */
4729
+ function isStaleTransitionStyleGroup(value) {
4730
+ return typeof value === "string" && value.startsWith(staleTransitionStyleGroupPrefix);
4731
+ }
4732
+ /** Drops a stale transition `styleGroup`, leaving authored values untouched. */
4733
+ function stripStaleTransitionStyleGroup(content) {
4734
+ if (!isStaleTransitionStyleGroup(content.styleGroup)) return { ...content };
4735
+ const { styleGroup: _stale, ...rest } = content;
4736
+ return rest;
4737
+ }
4597
4738
 
4598
4739
  //#endregion
4599
4740
  //#region ../theme-core/src/utils/tokenOrColor.ts
@@ -6657,10 +6798,10 @@ function resolveFocusIndicator(colour) {
6657
6798
  };
6658
6799
  }
6659
6800
  }
6660
- function resolveSolid(input, backgroundToken) {
6661
- const backgroundHex = paletteHex(input, backgroundToken);
6801
+ function resolveSolid(input, backgroundToken$1) {
6802
+ const backgroundHex = paletteHex(input, backgroundToken$1);
6662
6803
  if (!backgroundHex.ok) return backgroundHex;
6663
- const foregroundToken = unsafeAsOpaqueButtonColourToken(onColorTokenName(backgroundToken));
6804
+ const foregroundToken = unsafeAsOpaqueButtonColourToken(onColorTokenName(backgroundToken$1));
6664
6805
  const foregroundHex = input.palette[foregroundToken];
6665
6806
  if (typeof foregroundHex !== "string") {
6666
6807
  return {
@@ -6669,14 +6810,14 @@ function resolveSolid(input, backgroundToken) {
6669
6810
  tag: "missing_on_colour",
6670
6811
  variantId: input.variantId,
6671
6812
  state: input.state,
6672
- backgroundToken,
6813
+ backgroundToken: backgroundToken$1,
6673
6814
  foregroundToken
6674
6815
  }
6675
6816
  };
6676
6817
  }
6677
6818
  const contrast = validateButtonTextContrast({
6678
6819
  ...input,
6679
- background: backgroundToken,
6820
+ background: backgroundToken$1,
6680
6821
  backgroundHex: backgroundHex.hex,
6681
6822
  foreground: foregroundToken,
6682
6823
  foregroundHex
@@ -6694,9 +6835,9 @@ function resolveSolid(input, backgroundToken) {
6694
6835
  ok: true,
6695
6836
  colour: {
6696
6837
  kind: "solid",
6697
- backgroundToken,
6838
+ backgroundToken: backgroundToken$1,
6698
6839
  foregroundToken,
6699
- backgroundCss: tokenCss(backgroundToken),
6840
+ backgroundCss: tokenCss(backgroundToken$1),
6700
6841
  foregroundCss: tokenCss(foregroundToken)
6701
6842
  }
6702
6843
  };
@@ -6930,7 +7071,7 @@ function parseThemeForPersistence(value) {
6930
7071
  * overriding the twelve styles that deliberately declare none. Whether a button
6931
7072
  * carries a trailing affordance is the style's decision; the only caller that
6932
7073
  * legitimately opts out is the authored-button renderer, which reaches it
6933
- * through `themeButtonClassNameWithTrailingIcon`.
7074
+ * through `themeButtonExtraClassesWithTrailingIcon`.
6934
7075
  */
6935
7076
  const AFFORDANCE_SUPPRESS_CLASS = "rb-affordance-off";
6936
7077
 
@@ -6970,19 +7111,10 @@ function themeButtonSelector(variant, size) {
6970
7111
  }
6971
7112
  return `${baseSelector}.${themeButtonSizeClassName(size)}`;
6972
7113
  }
6973
- function themeButtonClassName(spec) {
6974
- const classes = [
6975
- ...themeButtonVariantClassNames(spec.variant),
6976
- themeButtonSizeClassName(spec.size),
6977
- spec.extraClassName
6978
- ];
6979
- return classes.filter(Boolean).join(" ");
6980
- }
6981
7114
  /**
6982
- * Button classes for an authored button that renders its own trailing icon.
7115
+ * Extra classes for an authored button that renders its own trailing icon.
6983
7116
  *
6984
- * A separate function rather than a flag on `themeButtonClassName`, because
6985
- * there is exactly one caller and it should stay visible in a diff. The name
7117
+ * This intent-named function has exactly one caller. The name
6986
7118
  * states what the caller knows to be true of its own markup — that it renders a
6987
7119
  * trailing icon — rather than what the theme does about it. The consequence is
6988
7120
  * the theme's to decide and to change; a caller naming the consequence would be
@@ -6993,11 +7125,8 @@ function themeButtonClassName(spec) {
6993
7125
  * affordance is declared by the button style, and a block overriding that is the
6994
7126
  * defect this replaced.
6995
7127
  */
6996
- function themeButtonClassNameWithTrailingIcon(spec) {
6997
- return themeButtonClassName({
6998
- ...spec,
6999
- extraClassName: [spec.extraClassName, AFFORDANCE_SUPPRESS_CLASS].filter(Boolean).join(" ")
7000
- });
7128
+ function themeButtonExtraClassesWithTrailingIcon() {
7129
+ return AFFORDANCE_SUPPRESS_CLASS;
7001
7130
  }
7002
7131
 
7003
7132
  //#endregion
@@ -13407,11 +13536,11 @@ const TEXT_CONTRAST_THRESHOLD = 3;
13407
13536
  * @param palette - Palette with color tokens (either ExpandedPalette or simple Record<string, string>)
13408
13537
  * @returns Array of text color token names that meet contrast threshold (3:1)
13409
13538
  */
13410
- function getLegibleTextColors(backgroundToken, palette) {
13411
- if (!backgroundToken) {
13539
+ function getLegibleTextColors(backgroundToken$1, palette) {
13540
+ if (!backgroundToken$1) {
13412
13541
  return getAllColorTokens(palette);
13413
13542
  }
13414
- const bgHex = palette[backgroundToken];
13543
+ const bgHex = palette[backgroundToken$1];
13415
13544
  if (!bgHex || typeof bgHex !== "string") {
13416
13545
  return getAllColorTokens(palette);
13417
13546
  }
@@ -15300,12 +15429,12 @@ function resolveAccessibleSurface(input) {
15300
15429
  const rejectedCandidates = [];
15301
15430
  const recipe = surfaceRecipes[input.surface];
15302
15431
  const threshold = contrastThresholdByPolicy[input.policy ?? "body-aa"];
15303
- for (const backgroundToken of recipe.backgroundCandidates) {
15304
- const backgroundHex = readPaletteHex(input.palette, backgroundToken);
15432
+ for (const backgroundToken$1 of recipe.backgroundCandidates) {
15433
+ const backgroundHex = readPaletteHex(input.palette, backgroundToken$1);
15305
15434
  if (!backgroundHex) {
15306
15435
  rejectedCandidates.push({
15307
15436
  kind: "background-candidate",
15308
- token: backgroundToken,
15437
+ token: backgroundToken$1,
15309
15438
  reason: "missing-token"
15310
15439
  });
15311
15440
  continue;
@@ -15313,7 +15442,7 @@ function resolveAccessibleSurface(input) {
15313
15442
  const heading = resolveRequiredForeground({
15314
15443
  role: "heading",
15315
15444
  candidates: recipe.headingCandidates,
15316
- backgroundToken,
15445
+ backgroundToken: backgroundToken$1,
15317
15446
  backgroundHex,
15318
15447
  palette: input.palette,
15319
15448
  threshold,
@@ -15322,7 +15451,7 @@ function resolveAccessibleSurface(input) {
15322
15451
  const text = resolveRequiredForeground({
15323
15452
  role: "text",
15324
15453
  candidates: recipe.textCandidates,
15325
- backgroundToken,
15454
+ backgroundToken: backgroundToken$1,
15326
15455
  backgroundHex,
15327
15456
  palette: input.palette,
15328
15457
  threshold,
@@ -15331,7 +15460,7 @@ function resolveAccessibleSurface(input) {
15331
15460
  const link = resolveRequiredForeground({
15332
15461
  role: "link",
15333
15462
  candidates: recipe.linkCandidates,
15334
- backgroundToken,
15463
+ backgroundToken: backgroundToken$1,
15335
15464
  backgroundHex,
15336
15465
  palette: input.palette,
15337
15466
  threshold,
@@ -15340,7 +15469,7 @@ function resolveAccessibleSurface(input) {
15340
15469
  if (!heading || !text || !link) continue;
15341
15470
  const supporting = resolveSupportingForeground({
15342
15471
  candidates: recipe.supportingCandidates,
15343
- backgroundToken,
15472
+ backgroundToken: backgroundToken$1,
15344
15473
  backgroundHex,
15345
15474
  text,
15346
15475
  palette: input.palette,
@@ -15351,7 +15480,7 @@ function resolveAccessibleSurface(input) {
15351
15480
  kind: "resolved-accessible-surface",
15352
15481
  surface: input.surface,
15353
15482
  background: {
15354
- token: asAccessibleSurfaceToken(backgroundToken),
15483
+ token: asAccessibleSurfaceToken(backgroundToken$1),
15355
15484
  hex: backgroundHex,
15356
15485
  inherited: recipe.inheritedBackground
15357
15486
  },
@@ -15450,23 +15579,23 @@ function resolveSafeToken(input) {
15450
15579
  }
15451
15580
  return null;
15452
15581
  }
15453
- function expandForegroundCandidates(candidates, backgroundToken) {
15582
+ function expandForegroundCandidates(candidates, backgroundToken$1) {
15454
15583
  const seen = new Set();
15455
15584
  const expanded = [];
15456
15585
  for (const candidate$1 of candidates) {
15457
- const token = expandForegroundCandidate(candidate$1, backgroundToken);
15586
+ const token = expandForegroundCandidate(candidate$1, backgroundToken$1);
15458
15587
  if (seen.has(token)) continue;
15459
15588
  seen.add(token);
15460
15589
  expanded.push(token);
15461
15590
  }
15462
15591
  return expanded;
15463
15592
  }
15464
- function expandForegroundCandidate(candidate$1, backgroundToken) {
15593
+ function expandForegroundCandidate(candidate$1, backgroundToken$1) {
15465
15594
  if (typeof candidate$1 === "string") {
15466
15595
  return candidate$1;
15467
15596
  }
15468
15597
  switch (candidate$1.kind) {
15469
- case "on-background": return onColorTokenName(backgroundToken);
15598
+ case "on-background": return onColorTokenName(backgroundToken$1);
15470
15599
  }
15471
15600
  }
15472
15601
  function readPaletteHex(palette, token) {
@@ -15600,7 +15729,6 @@ function blockDesignPlanFromAppearancePreset(preset, input) {
15600
15729
  return {
15601
15730
  ...planInput,
15602
15731
  appearancePresetId: asAppearancePresetId(preset.id),
15603
- transitionAfter: "none",
15604
15732
  emphasis: "medium",
15605
15733
  ...resolveBlockAppearancePresetDesign(preset, layoutProfile ?? null)
15606
15734
  };
@@ -16475,235 +16603,339 @@ function cardRoleFromLegacyVocabulary(value) {
16475
16603
  const cardRoleWithLegacyAliasSchema = z.preprocess((value) => cardRoleFromLegacyVocabulary(value) ?? value, cardRoleSchema);
16476
16604
 
16477
16605
  //#endregion
16478
- //#region ../theme-core/src/site-styles/compositionBudget.ts
16479
- const repairActionsByRatioIssueKind = {
16480
- "accent-density-exceeded": ["reduce-accent-density"],
16481
- "panel-density-exceeded": ["reduce-panel-density"],
16482
- "transition-mix-exceeded": ["reduce-transition-mix"],
16483
- "media-density-exceeded": ["reduce-media-density"],
16484
- "decorative-repetition-exceeded": ["reduce-decorative-repetition"]
16485
- };
16486
- function issueMetadata(policy, allowedRepairActions) {
16487
- return {
16488
- policy,
16489
- severity: designIssueSeverityForPolicy(policy),
16490
- allowedRepairActions
16606
+ //#region ../theme-core/src/site-styles/runtimeBackgroundResolution.ts
16607
+ function parseCanonicalRuntimeBackground(content) {
16608
+ const sectionStyles = readRecord(content._sectionStyles);
16609
+ const background = readRecord(sectionStyles?.background);
16610
+ if (!background) return { kind: "none" };
16611
+ const parsed = boxBackgroundSchema.safeParse(background);
16612
+ if (!parsed.success) return { kind: "none" };
16613
+ return parsed.data.contentOwner === pageDesignEditorBackgroundOverrideOwner ? {
16614
+ kind: "page-design-override",
16615
+ background: parsed.data
16616
+ } : {
16617
+ kind: "block-authored",
16618
+ background: parsed.data
16491
16619
  };
16492
16620
  }
16493
- /** Returns the fraction of page blocks selected by a budget check. */
16494
- function ratio(count, total) {
16495
- return total === 0 ? 0 : count / total;
16496
- }
16497
- function maxCompositionItemsWithinRatio(totalItems, allowedRatio) {
16498
- return Math.floor((allowedRatio + Number.EPSILON) * totalItems);
16499
- }
16500
- function isRatioExceeded(actualCount, totalItems, allowedRatio) {
16501
- return actualCount > maxCompositionItemsWithinRatio(totalItems, allowedRatio);
16502
- }
16503
- function issueWhenRatioExceeded(input) {
16504
- const actualRatio = ratio(input.blockIds.length, input.totalBlocks);
16505
- if (!isRatioExceeded(input.blockIds.length, input.totalBlocks, input.allowedRatio)) {
16506
- return null;
16621
+ function resolveSectionBackground(source, lookDefault) {
16622
+ if (source.kind === "page-design-override") {
16623
+ return {
16624
+ kind: "page-design-override",
16625
+ background: source.background
16626
+ };
16507
16627
  }
16508
- return {
16509
- kind: input.kind,
16510
- budget: input.budget,
16511
- actualRatio,
16512
- allowedRatio: input.allowedRatio,
16513
- blockIds: input.blockIds,
16514
- ...issueMetadata(input.policy, repairActionsByRatioIssueKind[input.kind])
16515
- };
16628
+ if (lookDefault) {
16629
+ if (source.kind === "block-authored" && (source.background.type === "image" || source.background.type === "video")) {
16630
+ return {
16631
+ kind: "block-authored",
16632
+ background: mergeLookForegroundWithAuthoredMedia(lookDefault, source.background)
16633
+ };
16634
+ }
16635
+ return {
16636
+ kind: "look-default",
16637
+ background: lookDefault
16638
+ };
16639
+ }
16640
+ if (source.kind === "block-authored" && (source.background.type === "image" || source.background.type === "video")) {
16641
+ return {
16642
+ kind: "block-authored",
16643
+ background: source.background
16644
+ };
16645
+ }
16646
+ return source.kind === "block-authored" ? {
16647
+ kind: "block-authored",
16648
+ background: source.background
16649
+ } : { kind: "none" };
16516
16650
  }
16517
- /** Checks whether block framing or a card surface contributes panel mass. */
16518
- function hasPanelFrameOrCardSurface(block) {
16519
- return block.appliedContentFrame.kind !== "plain" || block.appliedCardRole != null && block.appliedCardRole !== "none";
16651
+ function resolveSectionForeground(background, palette) {
16652
+ return resolveSurfaceForeground(background.kind === "none" ? null : background.background, palette, {}, { compositeBase: "background" });
16520
16653
  }
16521
- /** Checks explicit decorative annotations; transitions are budgeted separately. */
16522
- function hasDecorativeAnnotation(context) {
16523
- return context?.decorativeIntent === "supporting" || context?.decorativeIntent === "feature";
16654
+ /**
16655
+ * Read foreground authority from post-compiler block content. The preview
16656
+ * compiler has already materialized any Look-owned background into
16657
+ * `_sectionStyles`; raw authored content must be composed with its Look through
16658
+ * `resolveSectionBackground` instead.
16659
+ */
16660
+ function resolveCompiledSectionForegroundFromContent(content, palette) {
16661
+ if (!content) return resolveSurfaceForeground(null, palette);
16662
+ return resolveSectionForeground(resolveSectionBackground(parseCanonicalRuntimeBackground(content), null), palette);
16524
16663
  }
16525
- /** Checks whether a block annotation says media contributes visual weight. */
16526
- function hasMediaAnnotation(context) {
16527
- return context?.mediaIntent === "supporting" || context?.mediaIntent === "dominant";
16664
+ function mergeLookForegroundWithAuthoredMedia(lookDefault, authored) {
16665
+ const merged = boxBackgroundSchema.safeParse({
16666
+ ...lookDefault,
16667
+ type: authored.type,
16668
+ image: authored.image,
16669
+ objectFit: authored.objectFit,
16670
+ scale: authored.scale,
16671
+ position: authored.position
16672
+ });
16673
+ return merged.success ? merged.data : authored;
16528
16674
  }
16529
- function validateCompositionBudget(input) {
16530
- const { budget, policy, resolved } = input;
16531
- const totalBlocks = resolved.blocks.length;
16532
- const knownBlockIds = new Set(resolved.blocks.map((block) => block.blockId));
16533
- const blockContexts = input.blockContexts ?? [];
16534
- const unknownContextBlockIds = blockContexts.filter((context) => !knownBlockIds.has(context.blockId)).map((context) => context.blockId);
16535
- const knownBlockContexts = blockContexts.filter((context) => knownBlockIds.has(context.blockId));
16536
- const contextsByBlockId = new Map(knownBlockContexts.map((context) => [context.blockId, context]));
16537
- const issues = [];
16538
- if (unknownContextBlockIds.length > 0) {
16539
- issues.push({
16540
- kind: "unknown-composition-context-block",
16541
- blockIds: unknownContextBlockIds,
16542
- ...issueMetadata(policy, [])
16543
- });
16544
- }
16545
- const accentBlockIds = resolved.blocks.filter((block) => block.appliedSectionSurface === "accent-band" || block.appliedSectionSurface === "brand-depth-band").map((block) => block.blockId);
16546
- const accentIssue = issueWhenRatioExceeded({
16547
- kind: "accent-density-exceeded",
16548
- policy,
16549
- budget: budget.accentDensity,
16550
- blockIds: accentBlockIds,
16551
- totalBlocks,
16552
- allowedRatio: compositionDensityRatioLimits[budget.accentDensity]
16553
- });
16554
- if (accentIssue) {
16555
- issues.push(accentIssue);
16556
- }
16557
- const panelBlockIds = resolved.blocks.filter(hasPanelFrameOrCardSurface).map((block) => block.blockId);
16558
- const panelIssue = issueWhenRatioExceeded({
16559
- kind: "panel-density-exceeded",
16560
- policy,
16561
- budget: budget.panelDensity,
16562
- blockIds: panelBlockIds,
16563
- totalBlocks,
16564
- allowedRatio: compositionDensityRatioLimits[budget.panelDensity]
16565
- });
16566
- if (panelIssue) {
16567
- issues.push(panelIssue);
16568
- }
16569
- const transitionBlockIds = resolved.blocks.filter((block) => block.appliedTransitionAfter !== "none").map((block) => block.blockId);
16570
- const transitionIssue = issueWhenRatioExceeded({
16571
- kind: "transition-mix-exceeded",
16572
- policy,
16573
- budget: budget.transitionMix,
16574
- blockIds: transitionBlockIds,
16575
- totalBlocks,
16576
- allowedRatio: compositionTransitionMixRatioLimits[budget.transitionMix]
16577
- });
16578
- if (transitionIssue) {
16579
- issues.push(transitionIssue);
16580
- }
16581
- const ctaContexts = knownBlockContexts.filter((context) => (context.primaryCtaCount ?? 0) > 0);
16582
- const actualPrimaryCtaCount = ctaContexts.reduce((sum, context) => sum + (context.primaryCtaCount ?? 0), 0);
16583
- if (actualPrimaryCtaCount > budget.primaryCtaLimit) {
16584
- issues.push({
16585
- kind: "primary-cta-limit-exceeded",
16586
- actualCount: actualPrimaryCtaCount,
16587
- allowedCount: budget.primaryCtaLimit,
16588
- blockIds: ctaContexts.map((context) => context.blockId),
16589
- ...issueMetadata(policy, ["reduce-primary-ctas"])
16590
- });
16591
- }
16592
- const mediaBlockIds = resolved.blocks.filter((block) => hasMediaAnnotation(contextsByBlockId.get(block.blockId))).map((block) => block.blockId);
16593
- const mediaIssue = issueWhenRatioExceeded({
16594
- kind: "media-density-exceeded",
16595
- policy,
16596
- budget: budget.mediaDensity,
16597
- blockIds: mediaBlockIds,
16598
- totalBlocks,
16599
- allowedRatio: compositionDensityRatioLimits[budget.mediaDensity]
16600
- });
16601
- if (mediaIssue) {
16602
- issues.push(mediaIssue);
16603
- }
16604
- const decorativeBlockIds = resolved.blocks.filter((block) => hasDecorativeAnnotation(contextsByBlockId.get(block.blockId))).map((block) => block.blockId);
16605
- const decorativeIssue = issueWhenRatioExceeded({
16606
- kind: "decorative-repetition-exceeded",
16607
- policy,
16608
- budget: budget.decorativeRepetition,
16609
- blockIds: decorativeBlockIds,
16610
- totalBlocks,
16611
- allowedRatio: compositionDensityRatioLimits[budget.decorativeRepetition]
16612
- });
16613
- if (decorativeIssue) {
16614
- issues.push(decorativeIssue);
16615
- }
16616
- return issues;
16675
+ function readRecord(value) {
16676
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
16617
16677
  }
16618
16678
 
16619
16679
  //#endregion
16620
- //#region ../theme-core/src/site-styles/compilationPlan.ts
16621
- const defaultHeaderBrandPreferences = {
16622
- brandDisplay: "auto",
16623
- logoSize: "auto"
16624
- };
16625
- function headerBrandPreferencesFromTheme(theme) {
16626
- return {
16627
- brandDisplay: theme?.header.logo?.brandDisplay ?? defaultHeaderBrandPreferences.brandDisplay,
16628
- logoSize: theme?.header.logo?.size ?? defaultHeaderBrandPreferences.logoSize
16629
- };
16630
- }
16631
- function replaceSiteStyleCompilationPlan(input) {
16632
- return {
16633
- kind: "replace-site-style",
16634
- buttons: {
16635
- kind: "recompile",
16636
- selection: input.buttonStyleSelection ?? null,
16637
- reason: "site-style-replacement"
16638
- },
16639
- header: {
16640
- kind: "recompile",
16641
- selection: input.headerSelection,
16642
- brandPresentation: input.headerLogo === undefined ? { kind: "use-compiled-defaults" } : {
16643
- kind: "apply-logo-context",
16644
- logo: input.headerLogo
16645
- }
16646
- },
16647
- footerLookId: input.footerLookId ?? null
16648
- };
16649
- }
16680
+ //#region ../theme-core/src/site-styles/transitionCatalogue.ts
16650
16681
  /**
16651
- * Writes the brand display / logo size preference onto the header's logo style.
16652
- *
16653
- * Moved here from preview-next in #1318 Phase 4a so the header preference
16654
- * pipeline can be composed once, in theme-core, rather than half in the client.
16682
+ * Family order for pickers. Entries below are listed family by family in this
16683
+ * same order, so the flat option list and the grouped families agree.
16655
16684
  */
16656
- function withHeaderBrandThemePreferences(theme, preferences) {
16657
- const currentLogo = theme.header.logo;
16658
- if (!currentLogo && preferences.brandDisplay === defaultHeaderBrandPreferences.brandDisplay && preferences.logoSize === defaultHeaderBrandPreferences.logoSize) {
16659
- return theme;
16685
+ const transitionCatalogueFamilies = [
16686
+ {
16687
+ id: "gradient",
16688
+ label: "Gradient"
16689
+ },
16690
+ {
16691
+ id: "wave",
16692
+ label: "Wave"
16693
+ },
16694
+ {
16695
+ id: "angle",
16696
+ label: "Angle"
16660
16697
  }
16661
- return {
16662
- ...theme,
16663
- header: {
16664
- ...theme.header,
16665
- logo: {
16666
- fontFamily: currentLogo?.fontFamily ?? "heading",
16667
- letterSpacing: currentLogo?.letterSpacing ?? "normal",
16668
- gradient: currentLogo?.gradient ?? false,
16669
- ...currentLogo ?? {},
16670
- brandDisplay: preferences.brandDisplay,
16671
- size: preferences.logoSize
16698
+ ];
16699
+ const mediumHeight = "6rem + 2px";
16700
+ const compactHeight = "4.5rem + 2px";
16701
+ const largeHeight = "7rem + 2px";
16702
+ /**
16703
+ * The single source of transition vocabulary. Order is significant: it is the
16704
+ * order of `boundaryOptions` (after `none`) and of each family's variants,
16705
+ * which persisted pins, page-flow presets and the editor picker all rely on.
16706
+ *
16707
+ * Shapes match what `generateLayoutCss` draws today. Structured
16708
+ * amplitude/frequency treatments (issue 826) extend this list later.
16709
+ */
16710
+ const transitionCatalogueEntries = defineExactCatalog()([
16711
+ {
16712
+ intent: "soft-fade-short",
16713
+ optionId: asBoundaryOptionId("break:soft"),
16714
+ family: "gradient",
16715
+ variantId: "soft",
16716
+ label: "Soft",
16717
+ variantLabel: "Soft",
16718
+ treatment: {
16719
+ kind: "soft-fade",
16720
+ height: "medium",
16721
+ curve: "soft"
16722
+ },
16723
+ size: "medium",
16724
+ heightExpression: mediumHeight,
16725
+ shape: { kind: "fade" }
16726
+ },
16727
+ {
16728
+ intent: "soft-fade-long",
16729
+ optionId: asBoundaryOptionId("break:spacious"),
16730
+ family: "gradient",
16731
+ variantId: "spacious",
16732
+ label: "Spacious",
16733
+ variantLabel: "Spacious",
16734
+ treatment: {
16735
+ kind: "soft-fade",
16736
+ height: "large",
16737
+ curve: "spacious"
16738
+ },
16739
+ size: "spacious",
16740
+ heightExpression: "10rem",
16741
+ shape: { kind: "fade" }
16742
+ },
16743
+ {
16744
+ intent: "soft-fade-expansive",
16745
+ optionId: asBoundaryOptionId("break:expansive"),
16746
+ family: "gradient",
16747
+ variantId: "expansive",
16748
+ label: "Expansive",
16749
+ variantLabel: "Expansive",
16750
+ treatment: {
16751
+ kind: "soft-fade",
16752
+ height: "expansive",
16753
+ curve: "expansive"
16754
+ },
16755
+ size: "expansive",
16756
+ heightExpression: "14rem",
16757
+ shape: { kind: "fade" }
16758
+ },
16759
+ {
16760
+ intent: "wave-edge-balanced",
16761
+ optionId: asBoundaryOptionId("break:wave-balanced"),
16762
+ family: "wave",
16763
+ variantId: "balanced",
16764
+ label: "Balanced wave",
16765
+ variantLabel: "Balanced",
16766
+ treatment: {
16767
+ kind: "wave-edge",
16768
+ height: "medium",
16769
+ amplitude: "balanced",
16770
+ rhythm: "double",
16771
+ symmetry: "symmetrical"
16772
+ },
16773
+ size: "medium",
16774
+ heightExpression: mediumHeight,
16775
+ shape: {
16776
+ kind: "wave",
16777
+ mask: "balanced"
16778
+ }
16779
+ },
16780
+ {
16781
+ intent: "wave-edge",
16782
+ optionId: asBoundaryOptionId("break:wave"),
16783
+ family: "wave",
16784
+ variantId: "classic",
16785
+ label: "Classic wave",
16786
+ variantLabel: "Classic",
16787
+ treatment: {
16788
+ kind: "wave-edge",
16789
+ height: "medium",
16790
+ amplitude: "balanced",
16791
+ rhythm: "double",
16792
+ symmetry: "asymmetrical"
16793
+ },
16794
+ size: "medium",
16795
+ heightExpression: mediumHeight,
16796
+ shape: {
16797
+ kind: "wave",
16798
+ mask: "classic"
16799
+ }
16800
+ },
16801
+ {
16802
+ intent: "wave-edge-gentle",
16803
+ optionId: asBoundaryOptionId("break:wave-gentle"),
16804
+ family: "wave",
16805
+ variantId: "gentle",
16806
+ label: "Gentle wave",
16807
+ variantLabel: "Gentle",
16808
+ treatment: {
16809
+ kind: "wave-edge",
16810
+ height: "compact",
16811
+ amplitude: "subtle",
16812
+ rhythm: "single",
16813
+ symmetry: "symmetrical"
16814
+ },
16815
+ size: "compact",
16816
+ heightExpression: compactHeight,
16817
+ shape: {
16818
+ kind: "wave",
16819
+ mask: "gentle"
16820
+ }
16821
+ },
16822
+ {
16823
+ intent: "wave-edge-bold",
16824
+ optionId: asBoundaryOptionId("break:wave-bold"),
16825
+ family: "wave",
16826
+ variantId: "bold",
16827
+ label: "Bold wave",
16828
+ variantLabel: "Bold",
16829
+ treatment: {
16830
+ kind: "wave-edge",
16831
+ height: "large",
16832
+ amplitude: "bold",
16833
+ rhythm: "double",
16834
+ symmetry: "symmetrical"
16835
+ },
16836
+ size: "large",
16837
+ heightExpression: largeHeight,
16838
+ shape: {
16839
+ kind: "wave",
16840
+ mask: "bold"
16841
+ }
16842
+ },
16843
+ {
16844
+ intent: "wave-edge-drift",
16845
+ optionId: asBoundaryOptionId("break:wave-drift"),
16846
+ family: "wave",
16847
+ variantId: "drift",
16848
+ label: "Drift wave",
16849
+ variantLabel: "Drift",
16850
+ treatment: {
16851
+ kind: "wave-edge",
16852
+ height: "medium",
16853
+ amplitude: "balanced",
16854
+ rhythm: "double",
16855
+ symmetry: "drift"
16856
+ },
16857
+ size: "medium",
16858
+ heightExpression: mediumHeight,
16859
+ shape: {
16860
+ kind: "wave",
16861
+ mask: "drift"
16862
+ }
16863
+ },
16864
+ {
16865
+ intent: "angle-edge-subtle",
16866
+ optionId: asBoundaryOptionId("break:angle-subtle"),
16867
+ family: "angle",
16868
+ variantId: "subtle",
16869
+ label: "Subtle angle",
16870
+ variantLabel: "Subtle",
16871
+ treatment: {
16872
+ kind: "angle-edge",
16873
+ height: "compact",
16874
+ slope: "subtle",
16875
+ direction: "forward"
16876
+ },
16877
+ size: "compact",
16878
+ heightExpression: compactHeight,
16879
+ shape: {
16880
+ kind: "angle",
16881
+ polygon: {
16882
+ startTopPercent: 34,
16883
+ endTopPercent: 18
16672
16884
  }
16673
16885
  }
16674
- };
16675
- }
16676
-
16677
- //#endregion
16678
- //#region ../theme-core/src/site-styles/schemaVersionedJson.ts
16679
- function parseSchemaVersionedJson(input) {
16680
- if (input.value == null) {
16681
- return { kind: "absent" };
16682
- }
16683
- if (typeof input.value === "object" && input.value !== null && "schemaVersion" in input.value && input.value.schemaVersion !== input.expectedSchemaVersion) {
16684
- return {
16685
- kind: "invalid",
16686
- issue: {
16687
- kind: "unsupported-schema-version",
16688
- message: input.unsupportedSchemaVersionMessage
16886
+ },
16887
+ {
16888
+ intent: "angle-edge",
16889
+ optionId: asBoundaryOptionId("break:angle-balanced"),
16890
+ family: "angle",
16891
+ variantId: "balanced",
16892
+ label: "Balanced angle",
16893
+ variantLabel: "Balanced",
16894
+ treatment: {
16895
+ kind: "angle-edge",
16896
+ height: "medium",
16897
+ slope: "balanced",
16898
+ direction: "forward"
16899
+ },
16900
+ size: "medium",
16901
+ heightExpression: mediumHeight,
16902
+ shape: {
16903
+ kind: "angle",
16904
+ polygon: {
16905
+ startTopPercent: 42,
16906
+ endTopPercent: 8
16689
16907
  }
16690
- };
16691
- }
16692
- const parsed = input.schema.safeParse(input.value);
16693
- if (!parsed.success) {
16694
- const firstIssue = parsed.error.issues[0];
16695
- return {
16696
- kind: "invalid",
16697
- issue: {
16698
- kind: "invalid-json-shape",
16699
- message: firstIssue?.message ?? input.invalidShapeMessage
16908
+ }
16909
+ },
16910
+ {
16911
+ intent: "angle-edge-steep",
16912
+ optionId: asBoundaryOptionId("break:angle-steep"),
16913
+ family: "angle",
16914
+ variantId: "steep",
16915
+ label: "Steep angle",
16916
+ variantLabel: "Steep",
16917
+ treatment: {
16918
+ kind: "angle-edge",
16919
+ height: "large",
16920
+ slope: "steep",
16921
+ direction: "forward"
16922
+ },
16923
+ size: "large",
16924
+ heightExpression: largeHeight,
16925
+ shape: {
16926
+ kind: "angle",
16927
+ polygon: {
16928
+ startTopPercent: 54,
16929
+ endTopPercent: 0
16700
16930
  }
16701
- };
16931
+ }
16702
16932
  }
16703
- return {
16704
- kind: "parsed",
16705
- value: parsed.data
16706
- };
16933
+ ]);
16934
+ const transitionCatalogueCoversEveryIntentOnce = true;
16935
+ void transitionCatalogueCoversEveryIntentOnce;
16936
+ /** CSS `clip-path` value for an angle shape, as `generateLayoutCss` draws it. */
16937
+ function anglePolygonCss(polygon) {
16938
+ return `polygon(0 ${polygon.startTopPercent}%, 100% ${polygon.endTopPercent}%, 100% 100%, 0 100%)`;
16707
16939
  }
16708
16940
 
16709
16941
  //#endregion
@@ -16716,22 +16948,27 @@ function boundaryRuntimeMetrics(family, size, heightExpression) {
16716
16948
  reserveCss: `calc((${heightExpression}) / 2)`
16717
16949
  };
16718
16950
  }
16719
- const sectionBoundaryRuntimeMetricsByTransition = {
16720
- none: null,
16721
- "soft-fade-short": boundaryRuntimeMetrics("gradient", "medium", "6rem + 2px"),
16722
- "soft-fade-long": boundaryRuntimeMetrics("gradient", "spacious", "10rem"),
16723
- "soft-fade-expansive": boundaryRuntimeMetrics("gradient", "expansive", "14rem"),
16724
- "wave-edge": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
16725
- "wave-edge-balanced": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
16726
- "wave-edge-gentle": boundaryRuntimeMetrics("wave", "compact", "4.5rem + 2px"),
16727
- "wave-edge-bold": boundaryRuntimeMetrics("wave", "large", "7rem + 2px"),
16728
- "wave-edge-drift": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
16729
- "angle-edge": boundaryRuntimeMetrics("angle", "medium", "6rem + 2px"),
16730
- "angle-edge-subtle": boundaryRuntimeMetrics("angle", "compact", "4.5rem + 2px"),
16731
- "angle-edge-steep": boundaryRuntimeMetrics("angle", "large", "7rem + 2px")
16732
- };
16733
- function boundaryRuntimeMetricsForTransition(transition) {
16734
- return sectionBoundaryRuntimeMetricsByTransition[transition];
16951
+ const catalogueEntryByIntent = new Map(transitionCatalogueEntries.map((entry) => [entry.intent, entry]));
16952
+ const metricsByIntent = new Map(transitionCatalogueEntries.map((entry) => [entry.intent, boundaryRuntimeMetrics(entry.family, entry.size, entry.heightExpression)]));
16953
+ /**
16954
+ * The catalogue's compile-time coverage check guarantees an entry per
16955
+ * decorative intent, so a miss here is a broken invariant, not bad input.
16956
+ */
16957
+ function requireCatalogueValue(values, intent) {
16958
+ const value = values.get(intent);
16959
+ if (value === undefined) {
16960
+ throw new Error(`Transition catalogue has no entry for ${intent}`);
16961
+ }
16962
+ return value;
16963
+ }
16964
+ function transitionCatalogueEntryForIntent(intent) {
16965
+ return requireCatalogueValue(catalogueEntryByIntent, intent);
16966
+ }
16967
+ function isDecorativeTransitionIntent(transition) {
16968
+ return transition !== "none";
16969
+ }
16970
+ function boundaryRuntimeMetricsForDecorativeTransition(transition) {
16971
+ return requireCatalogueValue(metricsByIntent, transition);
16735
16972
  }
16736
16973
  const noneBoundaryOption = {
16737
16974
  id: asBoundaryOptionId("break:none"),
@@ -16739,264 +16976,63 @@ const noneBoundaryOption = {
16739
16976
  transitionAfter: "none",
16740
16977
  treatment: { kind: "none" }
16741
16978
  };
16742
- const softBoundaryOption = {
16743
- id: asBoundaryOptionId("break:soft"),
16744
- label: "Soft",
16745
- transitionAfter: "soft-fade-short",
16746
- treatment: {
16747
- kind: "soft-fade",
16748
- height: "medium",
16749
- curve: "soft"
16750
- }
16751
- };
16752
- const spaciousBoundaryOption = {
16753
- id: asBoundaryOptionId("break:spacious"),
16754
- label: "Spacious",
16755
- transitionAfter: "soft-fade-long",
16756
- treatment: {
16757
- kind: "soft-fade",
16758
- height: "large",
16759
- curve: "spacious"
16760
- }
16761
- };
16762
- const expansiveBoundaryOption = {
16763
- id: asBoundaryOptionId("break:expansive"),
16764
- label: "Expansive",
16765
- transitionAfter: "soft-fade-expansive",
16766
- treatment: {
16767
- kind: "soft-fade",
16768
- height: "expansive",
16769
- curve: "expansive"
16770
- }
16771
- };
16772
- const waveBoundaryOption = {
16773
- id: asBoundaryOptionId("break:wave"),
16774
- label: "Classic wave",
16775
- transitionAfter: "wave-edge",
16776
- treatment: {
16777
- kind: "wave-edge",
16778
- height: "medium",
16779
- amplitude: "balanced",
16780
- rhythm: "double",
16781
- symmetry: "asymmetrical"
16782
- }
16783
- };
16784
- const balancedWaveBoundaryOption = {
16785
- id: asBoundaryOptionId("break:wave-balanced"),
16786
- label: "Balanced wave",
16787
- transitionAfter: "wave-edge-balanced",
16788
- treatment: {
16789
- kind: "wave-edge",
16790
- height: "medium",
16791
- amplitude: "balanced",
16792
- rhythm: "double",
16793
- symmetry: "symmetrical"
16794
- }
16795
- };
16796
- const gentleWaveBoundaryOption = {
16797
- id: asBoundaryOptionId("break:wave-gentle"),
16798
- label: "Gentle wave",
16799
- transitionAfter: "wave-edge-gentle",
16800
- treatment: {
16801
- kind: "wave-edge",
16802
- height: "compact",
16803
- amplitude: "subtle",
16804
- rhythm: "single",
16805
- symmetry: "symmetrical"
16806
- }
16807
- };
16808
- const boldWaveBoundaryOption = {
16809
- id: asBoundaryOptionId("break:wave-bold"),
16810
- label: "Bold wave",
16811
- transitionAfter: "wave-edge-bold",
16812
- treatment: {
16813
- kind: "wave-edge",
16814
- height: "large",
16815
- amplitude: "bold",
16816
- rhythm: "double",
16817
- symmetry: "symmetrical"
16818
- }
16819
- };
16820
- const driftWaveBoundaryOption = {
16821
- id: asBoundaryOptionId("break:wave-drift"),
16822
- label: "Drift wave",
16823
- transitionAfter: "wave-edge-drift",
16824
- treatment: {
16825
- kind: "wave-edge",
16826
- height: "medium",
16827
- amplitude: "balanced",
16828
- rhythm: "double",
16829
- symmetry: "drift"
16830
- }
16831
- };
16832
- const subtleAngleBoundaryOption = {
16833
- id: asBoundaryOptionId("break:angle-subtle"),
16834
- label: "Subtle angle",
16835
- transitionAfter: "angle-edge-subtle",
16836
- treatment: {
16837
- kind: "angle-edge",
16838
- height: "compact",
16839
- slope: "subtle",
16840
- direction: "forward"
16841
- }
16842
- };
16843
- const balancedAngleBoundaryOption = {
16844
- id: asBoundaryOptionId("break:angle-balanced"),
16845
- label: "Balanced angle",
16846
- transitionAfter: "angle-edge",
16847
- treatment: {
16848
- kind: "angle-edge",
16849
- height: "medium",
16850
- slope: "balanced",
16851
- direction: "forward"
16852
- }
16853
- };
16854
- const steepAngleBoundaryOption = {
16855
- id: asBoundaryOptionId("break:angle-steep"),
16856
- label: "Steep angle",
16857
- transitionAfter: "angle-edge-steep",
16858
- treatment: {
16859
- kind: "angle-edge",
16860
- height: "large",
16861
- slope: "steep",
16862
- direction: "forward"
16863
- }
16864
- };
16979
+ const boundaryOptionByIntent = new Map(transitionCatalogueEntries.map((entry) => [entry.intent, {
16980
+ id: entry.optionId,
16981
+ label: entry.label,
16982
+ transitionAfter: entry.intent,
16983
+ treatment: entry.treatment
16984
+ }]));
16985
+ function boundaryOptionForTransitionIntent(intent) {
16986
+ return requireCatalogueValue(boundaryOptionByIntent, intent);
16987
+ }
16988
+ const softBoundaryOption = boundaryOptionForTransitionIntent("soft-fade-short");
16989
+ const spaciousBoundaryOption = boundaryOptionForTransitionIntent("soft-fade-long");
16990
+ const expansiveBoundaryOption = boundaryOptionForTransitionIntent("soft-fade-expansive");
16991
+ const waveBoundaryOption = boundaryOptionForTransitionIntent("wave-edge");
16992
+ const balancedWaveBoundaryOption = boundaryOptionForTransitionIntent("wave-edge-balanced");
16993
+ const gentleWaveBoundaryOption = boundaryOptionForTransitionIntent("wave-edge-gentle");
16994
+ const boldWaveBoundaryOption = boundaryOptionForTransitionIntent("wave-edge-bold");
16995
+ const driftWaveBoundaryOption = boundaryOptionForTransitionIntent("wave-edge-drift");
16996
+ const subtleAngleBoundaryOption = boundaryOptionForTransitionIntent("angle-edge-subtle");
16997
+ const balancedAngleBoundaryOption = boundaryOptionForTransitionIntent("angle-edge");
16998
+ const steepAngleBoundaryOption = boundaryOptionForTransitionIntent("angle-edge-steep");
16999
+ /**
17000
+ * Must remain non-empty; downstream code indexes `[0]` as the documented default.
17001
+ */
17002
+ const boundaryOptions = [noneBoundaryOption, ...transitionCatalogueEntries.map((entry) => boundaryOptionForTransitionIntent(entry.intent))];
16865
17003
  const noneTransitionVariant = {
16866
17004
  familyId: "none",
16867
17005
  variantId: "none",
16868
17006
  label: "None",
16869
17007
  boundaryOption: noneBoundaryOption
16870
17008
  };
16871
- const softGradientTransitionVariant = {
16872
- familyId: "gradient",
16873
- variantId: "soft",
16874
- label: "Soft",
16875
- boundaryOption: softBoundaryOption
16876
- };
16877
- const spaciousGradientTransitionVariant = {
16878
- familyId: "gradient",
16879
- variantId: "spacious",
16880
- label: "Spacious",
16881
- boundaryOption: spaciousBoundaryOption
16882
- };
16883
- const expansiveGradientTransitionVariant = {
16884
- familyId: "gradient",
16885
- variantId: "expansive",
16886
- label: "Expansive",
16887
- boundaryOption: expansiveBoundaryOption
16888
- };
16889
- const balancedWaveTransitionVariant = {
16890
- familyId: "wave",
16891
- variantId: "balanced",
16892
- label: "Balanced",
16893
- boundaryOption: balancedWaveBoundaryOption
16894
- };
16895
- const classicWaveTransitionVariant = {
16896
- familyId: "wave",
16897
- variantId: "classic",
16898
- label: "Classic",
16899
- boundaryOption: waveBoundaryOption
16900
- };
16901
- const gentleWaveTransitionVariant = {
16902
- familyId: "wave",
16903
- variantId: "gentle",
16904
- label: "Gentle",
16905
- boundaryOption: gentleWaveBoundaryOption
16906
- };
16907
- const boldWaveTransitionVariant = {
16908
- familyId: "wave",
16909
- variantId: "bold",
16910
- label: "Bold",
16911
- boundaryOption: boldWaveBoundaryOption
16912
- };
16913
- const driftWaveTransitionVariant = {
16914
- familyId: "wave",
16915
- variantId: "drift",
16916
- label: "Drift",
16917
- boundaryOption: driftWaveBoundaryOption
16918
- };
16919
- const subtleAngleTransitionVariant = {
16920
- familyId: "angle",
16921
- variantId: "subtle",
16922
- label: "Subtle",
16923
- boundaryOption: subtleAngleBoundaryOption
16924
- };
16925
- const balancedAngleTransitionVariant = {
16926
- familyId: "angle",
16927
- variantId: "balanced",
16928
- label: "Balanced",
16929
- boundaryOption: balancedAngleBoundaryOption
16930
- };
16931
- const steepAngleTransitionVariant = {
16932
- familyId: "angle",
16933
- variantId: "steep",
16934
- label: "Steep",
16935
- boundaryOption: steepAngleBoundaryOption
16936
- };
16937
- const sectionTransitionFamilies = [
16938
- {
16939
- id: "none",
16940
- label: "None",
16941
- variants: [noneTransitionVariant]
16942
- },
16943
- {
16944
- id: "gradient",
16945
- label: "Gradient",
16946
- variants: [
16947
- softGradientTransitionVariant,
16948
- spaciousGradientTransitionVariant,
16949
- expansiveGradientTransitionVariant
16950
- ]
16951
- },
16952
- {
16953
- id: "wave",
16954
- label: "Wave",
16955
- variants: [
16956
- balancedWaveTransitionVariant,
16957
- classicWaveTransitionVariant,
16958
- gentleWaveTransitionVariant,
16959
- boldWaveTransitionVariant,
16960
- driftWaveTransitionVariant
16961
- ]
16962
- },
16963
- {
16964
- id: "angle",
16965
- label: "Angle",
16966
- variants: [
16967
- subtleAngleTransitionVariant,
16968
- balancedAngleTransitionVariant,
16969
- steepAngleTransitionVariant
16970
- ]
16971
- }
16972
- ];
16973
- /**
16974
- * Must remain non-empty; downstream code indexes `[0]` as the documented default.
16975
- */
16976
- const boundaryOptions = [
16977
- noneBoundaryOption,
16978
- softBoundaryOption,
16979
- spaciousBoundaryOption,
16980
- expansiveBoundaryOption,
16981
- balancedWaveBoundaryOption,
16982
- waveBoundaryOption,
16983
- gentleWaveBoundaryOption,
16984
- boldWaveBoundaryOption,
16985
- driftWaveBoundaryOption,
16986
- subtleAngleBoundaryOption,
16987
- balancedAngleBoundaryOption,
16988
- steepAngleBoundaryOption
16989
- ];
17009
+ function transitionVariantForEntry(entry) {
17010
+ return {
17011
+ familyId: entry.family,
17012
+ variantId: entry.variantId,
17013
+ label: entry.variantLabel,
17014
+ boundaryOption: boundaryOptionForTransitionIntent(entry.intent)
17015
+ };
17016
+ }
17017
+ const sectionTransitionFamilies = [{
17018
+ id: "none",
17019
+ label: "None",
17020
+ variants: [noneTransitionVariant]
17021
+ }, ...transitionCatalogueFamilies.map((family) => ({
17022
+ id: family.id,
17023
+ label: family.label,
17024
+ variants: requireNonEmptyReadonlyArray(transitionCatalogueEntries.filter((entry) => entry.family === family.id).map(transitionVariantForEntry), `Transition family ${family.id} has no catalogue entries.`)
17025
+ }))];
17026
+ const boundaryOptionById = new Map(boundaryOptions.map((option) => [option.id, option]));
17027
+ const boundaryOptionByTreatmentKey = new Map(boundaryOptions.map((option) => [stableTreatmentKey(option.treatment), option]));
17028
+ const transitionVariantByBoundaryOptionId = new Map(sectionTransitionFamilies.flatMap((family) => family.variants.map((variant) => [variant.boundaryOption.id, variant])));
17029
+ const transitionFamilyById = new Map(sectionTransitionFamilies.map((family) => [family.id, family]));
16990
17030
  function sectionTransitionVariantForBoundaryOptionId(boundaryOptionId) {
16991
17031
  if (!boundaryOptionId) return null;
16992
- for (const family of sectionTransitionFamilies) {
16993
- const variant = family.variants.find((candidate$1) => candidate$1.boundaryOption.id === boundaryOptionId);
16994
- if (variant) return variant;
16995
- }
16996
- return null;
17032
+ return transitionVariantByBoundaryOptionId.get(boundaryOptionId) ?? null;
16997
17033
  }
16998
17034
  function boundaryTreatmentForBoundaryOptionId(boundaryOptionId) {
16999
- return boundaryOptions.find((option) => option.id === boundaryOptionId)?.treatment ?? {
17035
+ return boundaryOptionById.get(boundaryOptionId)?.treatment ?? {
17000
17036
  kind: "legacy-unsupported",
17001
17037
  boundaryOptionId
17002
17038
  };
@@ -17005,11 +17041,19 @@ function boundaryOptionForTreatment(treatment) {
17005
17041
  if (treatment.kind === "legacy-unsupported") {
17006
17042
  return null;
17007
17043
  }
17008
- return boundaryOptions.find((option) => sectionBoundaryTreatmentsEqual(option.treatment, treatment)) ?? null;
17044
+ return boundaryOptionByTreatmentKey.get(stableTreatmentKey(treatment)) ?? null;
17009
17045
  }
17010
17046
  function boundaryOptionIdForTreatment(treatment) {
17011
17047
  return boundaryOptionForTreatment(treatment)?.id ?? (treatment.kind === "legacy-unsupported" ? treatment.boundaryOptionId : null);
17012
17048
  }
17049
+ /**
17050
+ * Supported decorative intent for a structured treatment, or null for `none`,
17051
+ * legacy-unsupported pins and structured treatments the catalogue lacks.
17052
+ */
17053
+ function transitionIntentForTreatment(treatment) {
17054
+ const transition = boundaryOptionForTreatment(treatment)?.transitionAfter;
17055
+ return transition && isDecorativeTransitionIntent(transition) ? transition : null;
17056
+ }
17013
17057
  function sectionBoundaryTreatmentsEqual(left, right) {
17014
17058
  return stableTreatmentKey(left) === stableTreatmentKey(right);
17015
17059
  }
@@ -17036,10 +17080,10 @@ function stableTreatmentKey(treatment) {
17036
17080
  function sectionTransitionFamilyForBoundaryOptionId(boundaryOptionId) {
17037
17081
  const variant = sectionTransitionVariantForBoundaryOptionId(boundaryOptionId);
17038
17082
  if (!variant) return null;
17039
- return sectionTransitionFamilies.find((family) => family.id === variant.familyId) ?? null;
17083
+ return transitionFamilyById.get(variant.familyId) ?? null;
17040
17084
  }
17041
17085
  function defaultSectionTransitionVariantForFamily(familyId) {
17042
- const family = sectionTransitionFamilies.find((candidate$1) => candidate$1.id === familyId);
17086
+ const family = transitionFamilyById.get(familyId);
17043
17087
  if (!family) {
17044
17088
  throw new Error(`Unknown section transition family: ${familyId}`);
17045
17089
  }
@@ -17047,409 +17091,807 @@ function defaultSectionTransitionVariantForFamily(familyId) {
17047
17091
  }
17048
17092
 
17049
17093
  //#endregion
17050
- //#region ../theme-core/src/site-styles/pageDesignIntent.ts
17051
- const pageDesignIntentSchemaVersion = 2;
17052
- const legacyPageDesignIntentSchemaVersion = 1;
17053
- const unknownBoundaryOptionId = asBoundaryOptionId("break:unknown");
17094
+ //#region ../theme-core/src/site-styles/sectionBoundaryComposition.ts
17095
+ const backgroundToken = requireThemeColorToken$1("background");
17096
+ /**
17097
+ * Classifies a section from its final render content. `_sectionStyles`
17098
+ * already holds the effective background (Look default, page-design override
17099
+ * and block-authored media merged by `resolveSectionBackground`), so this only
17100
+ * reads it and never re-decides precedence. A missing or unparseable
17101
+ * background is the page background, as it is for the renderer.
17102
+ */
17103
+ function boundaryEndpointForSectionContent(content) {
17104
+ const background = parseCanonicalRuntimeBackground(content);
17105
+ if (background.kind === "none") {
17106
+ return {
17107
+ kind: "paint",
17108
+ token: backgroundToken
17109
+ };
17110
+ }
17111
+ const effective = background.background;
17112
+ switch (effective.type) {
17113
+ case "color": {
17114
+ if (effective.color === null || effective.color === undefined) {
17115
+ return {
17116
+ kind: "paint",
17117
+ token: backgroundToken
17118
+ };
17119
+ }
17120
+ const token = parseThemeColorToken(effective.color);
17121
+ return token ? {
17122
+ kind: "paint",
17123
+ token
17124
+ } : {
17125
+ kind: "unpainted",
17126
+ reason: "literal-color"
17127
+ };
17128
+ }
17129
+ case "gradient": return {
17130
+ kind: "unpainted",
17131
+ reason: "gradient"
17132
+ };
17133
+ case "image":
17134
+ case "video": return {
17135
+ kind: "unpainted",
17136
+ reason: "media"
17137
+ };
17138
+ default: return assertNever$1(effective, "Unhandled section background type");
17139
+ }
17140
+ }
17054
17141
  /**
17055
- * Site style is site-owned authority. Page design snapshots retain pins and
17056
- * flow choices, but must not keep resolving against a superseded site style.
17142
+ * Reads both facts from final render content in one place. `_frameStyles` is
17143
+ * the compiled frame; content without one (no page design, an SDK block)
17144
+ * draws a plain frame.
17057
17145
  */
17058
- function rebasePageDesignIntentToSiteStyle(intent, siteStyleId) {
17059
- return intent.siteStyleId === siteStyleId ? intent : {
17060
- ...intent,
17061
- siteStyleId
17146
+ function sectionFactsForContent(content) {
17147
+ return {
17148
+ endpoint: boundaryEndpointForSectionContent(content),
17149
+ frame: frameKindForContent(content)
17062
17150
  };
17063
17151
  }
17064
- const pageDesignUserPinnedAppearanceChoiceSchema = z.object({
17065
- kind: z.literal("user-pinned"),
17066
- value: z.string().min(1).transform(asAppearancePresetId)
17067
- }).strict();
17068
- const pageDesignUserPinnedBoundaryChoiceSchema = z.object({
17069
- kind: z.literal("user-pinned"),
17070
- value: z.string().min(1).transform(asBoundaryOptionId)
17071
- }).strict();
17072
- const pageDesignBoundaryTreatmentSchema = z.discriminatedUnion("kind", [
17073
- z.object({ kind: z.literal("none") }).strict(),
17074
- z.object({
17075
- kind: z.literal("soft-fade"),
17076
- height: z.enum([
17077
- "compact",
17078
- "medium",
17079
- "large",
17080
- "expansive"
17081
- ]),
17082
- curve: z.enum([
17083
- "soft",
17084
- "spacious",
17085
- "expansive"
17086
- ])
17087
- }).strict(),
17088
- z.object({
17089
- kind: z.literal("wave-edge"),
17090
- height: z.enum([
17091
- "compact",
17092
- "medium",
17093
- "large"
17094
- ]),
17095
- amplitude: z.enum([
17096
- "subtle",
17097
- "balanced",
17098
- "bold"
17099
- ]),
17100
- rhythm: z.enum(["single", "double"]),
17101
- symmetry: z.enum([
17102
- "symmetrical",
17103
- "asymmetrical",
17104
- "drift"
17105
- ])
17106
- }).strict(),
17107
- z.object({
17108
- kind: z.literal("angle-edge"),
17109
- height: z.enum([
17110
- "compact",
17111
- "medium",
17112
- "large"
17113
- ]),
17114
- slope: z.enum([
17115
- "subtle",
17116
- "balanced",
17117
- "steep"
17118
- ]),
17119
- direction: z.enum(["forward", "reverse"])
17120
- }).strict(),
17121
- z.object({
17122
- kind: z.literal("legacy-unsupported"),
17123
- boundaryOptionId: z.string().min(1).transform(asBoundaryOptionId)
17124
- }).strict()
17125
- ]);
17126
- const pageDesignUserPinnedBoundaryTreatmentChoiceSchema = z.object({
17127
- kind: z.literal("user-pinned"),
17128
- value: pageDesignBoundaryTreatmentSchema
17129
- }).strict();
17130
- const pageDesignBlockLookPinSchema = z.object({
17131
- blockId: z.string().min(1).transform(asDesignBlockId),
17132
- blockKind: z.string().min(1),
17133
- choice: pageDesignUserPinnedAppearanceChoiceSchema
17134
- }).strict();
17135
- const pageDesignBoundaryPinSchema = z.object({
17136
- boundaryId: z.string().min(1).transform(asDesignBoundaryId),
17137
- previousBlockId: z.string().min(1).transform(asDesignBlockId),
17138
- nextBlockId: z.string().min(1).transform(asDesignBlockId),
17139
- choice: pageDesignUserPinnedBoundaryTreatmentChoiceSchema
17140
- }).strict().superRefine(validateBoundaryPinIdMatchesBlocks);
17141
- const legacyPageDesignBoundaryPinSchema = z.object({
17142
- boundaryId: z.string().min(1).transform(asDesignBoundaryId),
17143
- previousBlockId: z.string().min(1).transform(asDesignBlockId),
17144
- nextBlockId: z.string().min(1).transform(asDesignBlockId),
17145
- choice: pageDesignUserPinnedBoundaryChoiceSchema
17146
- }).strict().superRefine(validateBoundaryPinIdMatchesBlocks);
17152
+ function frameKindForContent(content) {
17153
+ const frameStyles = content._frameStyles;
17154
+ const kind = typeof frameStyles === "object" && frameStyles !== null && "kind" in frameStyles ? frameStyles.kind : null;
17155
+ return kind === "panel" || kind === "flush-panel" ? kind : "plain";
17156
+ }
17147
17157
  /**
17148
- * Persisted editor intent stores only user-pinned block Looks and boundary choices.
17149
- * Missing pins mean `auto`; block-kind/preset compatibility is enforced by resolution,
17150
- * not by this JSON parse boundary.
17158
+ * The frame rule: a flush panel owns the edge it runs to, so no break is
17159
+ * drawn next to one. The single home for this rule; the generated
17160
+ * pipeline's budget and the site-style lab ask it with their plan frames.
17151
17161
  */
17152
- const pageDesignIntentV1Schema = z.object({
17153
- schemaVersion: z.literal(legacyPageDesignIntentSchemaVersion),
17154
- siteStyleId: z.string().min(1).transform(asSiteStyleId),
17155
- pageFlowPresetId: z.string().min(1).transform(asPageFlowPresetId),
17156
- blockLookPins: z.array(pageDesignBlockLookPinSchema),
17157
- boundaryPins: z.array(legacyPageDesignBoundaryPinSchema)
17158
- }).strict().superRefine(validateUniquePageDesignPins);
17159
- const pageDesignIntentV2Schema = z.object({
17160
- schemaVersion: z.literal(pageDesignIntentSchemaVersion),
17161
- siteStyleId: z.string().min(1).transform(asSiteStyleId),
17162
- pageFlowPresetId: z.string().min(1).transform(asPageFlowPresetId),
17163
- blockLookPins: z.array(pageDesignBlockLookPinSchema),
17164
- boundaryPins: z.array(pageDesignBoundaryPinSchema)
17165
- }).strict().superRefine(validateUniquePageDesignPins);
17166
- function parsePageDesignIntent(value, options = {}) {
17167
- if (isLegacyPageDesignIntentCandidate(value)) {
17168
- const legacyParsed = parseSchemaVersionedJson({
17169
- value,
17170
- schema: pageDesignIntentV1Schema,
17171
- expectedSchemaVersion: legacyPageDesignIntentSchemaVersion,
17172
- unsupportedSchemaVersionMessage: options.unsupportedSchemaVersionMessage ?? "Unsupported Theme V2 page design intent schemaVersion.",
17173
- invalidShapeMessage: options.invalidShapeMessage ?? "Invalid Theme V2 page design intent shape."
17174
- });
17175
- switch (legacyParsed.kind) {
17176
- case "absent": return legacyParsed;
17177
- case "parsed": return {
17178
- kind: "parsed",
17179
- intent: normalizeLegacyPageDesignIntent(legacyParsed.value)
17180
- };
17181
- case "invalid": return legacyParsed;
17182
- default: return assertNever$1(legacyParsed, "Unhandled legacy page design intent JSON parse result");
17183
- }
17162
+ function frameNotShownReason(previous, next) {
17163
+ return previous === "flush-panel" || next === "flush-panel" ? "flush-panel" : null;
17164
+ }
17165
+ /**
17166
+ * Decides whether and how a join draws. The frame rule wins before paint is
17167
+ * considered; two unpainted sides show nothing.
17168
+ */
17169
+ function composeBoundary(previous, next) {
17170
+ const frameReason = frameNotShownReason(previous.frame, next.frame);
17171
+ if (frameReason) {
17172
+ return {
17173
+ kind: "not-shown",
17174
+ reason: frameReason
17175
+ };
17184
17176
  }
17185
- const parsed = parseSchemaVersionedJson({
17186
- value,
17187
- schema: pageDesignIntentV2Schema,
17188
- expectedSchemaVersion: pageDesignIntentSchemaVersion,
17189
- unsupportedSchemaVersionMessage: options.unsupportedSchemaVersionMessage ?? "Unsupported Theme V2 page design intent schemaVersion.",
17190
- invalidShapeMessage: options.invalidShapeMessage ?? "Invalid Theme V2 page design intent shape."
17191
- });
17192
- switch (parsed.kind) {
17193
- case "absent": return parsed;
17194
- case "parsed": return {
17195
- kind: "parsed",
17196
- intent: parsed.value
17177
+ const from = previous.endpoint;
17178
+ const to = next.endpoint;
17179
+ if (from.kind === "paint") {
17180
+ return {
17181
+ kind: "shown",
17182
+ composition: to.kind === "paint" ? {
17183
+ kind: "paint-to-paint",
17184
+ from: from.token,
17185
+ to: to.token
17186
+ } : {
17187
+ kind: "paint-to-unpainted",
17188
+ from: from.token
17189
+ }
17197
17190
  };
17198
- case "invalid": return parsed;
17199
- default: return assertNever$1(parsed, "Unhandled page design intent JSON parse result");
17200
17191
  }
17192
+ return to.kind === "paint" ? {
17193
+ kind: "shown",
17194
+ composition: {
17195
+ kind: "unpainted-to-paint",
17196
+ to: to.token
17197
+ }
17198
+ } : {
17199
+ kind: "not-shown",
17200
+ reason: "both-unpainted"
17201
+ };
17201
17202
  }
17202
- function isLegacyPageDesignIntentCandidate(value) {
17203
- return typeof value === "object" && value !== null && "schemaVersion" in value && value.schemaVersion === legacyPageDesignIntentSchemaVersion;
17203
+ /**
17204
+ * Placement, reserves and paint for a shown boundary, derived from the
17205
+ * composition and the catalogue's metrics for the treatment.
17206
+ */
17207
+ function boundaryLayout(intent, composition) {
17208
+ const metrics = boundaryRuntimeMetricsForDecorativeTransition(intent);
17209
+ switch (composition.kind) {
17210
+ case "paint-to-paint": return {
17211
+ placement: "centred",
17212
+ reserves: {
17213
+ before: metrics.reserveCss,
17214
+ after: metrics.reserveCss
17215
+ },
17216
+ paint: {
17217
+ band: composition.from,
17218
+ shape: composition.to
17219
+ }
17220
+ };
17221
+ case "unpainted-to-paint": return {
17222
+ placement: "previous",
17223
+ reserves: {
17224
+ before: metrics.heightCss,
17225
+ after: null
17226
+ },
17227
+ paint: {
17228
+ band: null,
17229
+ shape: composition.to
17230
+ }
17231
+ };
17232
+ case "paint-to-unpainted": return {
17233
+ placement: "next",
17234
+ reserves: {
17235
+ before: null,
17236
+ after: metrics.heightCss
17237
+ },
17238
+ paint: {
17239
+ band: null,
17240
+ shape: composition.from
17241
+ }
17242
+ };
17243
+ default: return assertNever$1(composition, "Unhandled boundary composition");
17244
+ }
17204
17245
  }
17205
- function normalizeLegacyPageDesignIntent(intent) {
17246
+ function requireThemeColorToken$1(value) {
17247
+ const token = parseThemeColorToken(value);
17248
+ if (!token) {
17249
+ throw new Error(`Expected a theme color token. Received: ${value}`);
17250
+ }
17251
+ return token;
17252
+ }
17253
+
17254
+ //#endregion
17255
+ //#region ../theme-core/src/site-styles/compositionBudget.ts
17256
+ const repairActionsByRatioIssueKind = {
17257
+ "accent-density-exceeded": ["reduce-accent-density"],
17258
+ "panel-density-exceeded": ["reduce-panel-density"],
17259
+ "transition-mix-exceeded": ["reduce-transition-mix"],
17260
+ "media-density-exceeded": ["reduce-media-density"],
17261
+ "decorative-repetition-exceeded": ["reduce-decorative-repetition"]
17262
+ };
17263
+ function issueMetadata(policy, allowedRepairActions) {
17206
17264
  return {
17207
- schemaVersion: pageDesignIntentSchemaVersion,
17208
- siteStyleId: intent.siteStyleId,
17209
- pageFlowPresetId: intent.pageFlowPresetId,
17210
- blockLookPins: intent.blockLookPins,
17211
- boundaryPins: intent.boundaryPins.map((pin) => ({
17212
- boundaryId: pin.boundaryId,
17213
- previousBlockId: pin.previousBlockId,
17214
- nextBlockId: pin.nextBlockId,
17215
- choice: userPinnedDesignChoice(boundaryTreatmentForBoundaryOptionId(pin.choice.value))
17216
- }))
17265
+ policy,
17266
+ severity: designIssueSeverityForPolicy(policy),
17267
+ allowedRepairActions
17217
17268
  };
17218
17269
  }
17219
- function autoDesignChoice() {
17220
- return { kind: "auto" };
17270
+ /** Returns the fraction of page blocks selected by a budget check. */
17271
+ function ratio(count, total) {
17272
+ return total === 0 ? 0 : count / total;
17221
17273
  }
17222
- function userPinnedDesignChoice(value) {
17274
+ function maxCompositionItemsWithinRatio(totalItems, allowedRatio) {
17275
+ return Math.floor((allowedRatio + Number.EPSILON) * totalItems);
17276
+ }
17277
+ function isRatioExceeded(actualCount, totalItems, allowedRatio) {
17278
+ return actualCount > maxCompositionItemsWithinRatio(totalItems, allowedRatio);
17279
+ }
17280
+ function issueWhenRatioExceeded(input) {
17281
+ const actualRatio = ratio(input.blockIds.length, input.totalBlocks);
17282
+ if (!isRatioExceeded(input.blockIds.length, input.totalBlocks, input.allowedRatio)) {
17283
+ return null;
17284
+ }
17223
17285
  return {
17224
- kind: "user-pinned",
17225
- value
17286
+ kind: input.kind,
17287
+ budget: input.budget,
17288
+ actualRatio,
17289
+ allowedRatio: input.allowedRatio,
17290
+ blockIds: input.blockIds,
17291
+ ...issueMetadata(input.policy, repairActionsByRatioIssueKind[input.kind])
17226
17292
  };
17227
17293
  }
17228
- function resolvePageDesignBlockLookChoice(intent, blockId) {
17229
- return intent.blockLookPins.find((pin) => pin.blockId === blockId)?.choice ?? autoDesignChoice();
17294
+ /** Checks whether block framing or a card surface contributes panel mass. */
17295
+ function hasPanelFrameOrCardSurface(block) {
17296
+ return block.appliedContentFrame.kind !== "plain" || block.appliedCardRole != null && block.appliedCardRole !== "none";
17230
17297
  }
17231
- function resolvePageDesignBoundaryChoice(intent, previousBlockId, nextBlockId) {
17232
- return intent.boundaryPins.find((pin) => pin.previousBlockId === previousBlockId && pin.nextBlockId === nextBlockId)?.choice ?? autoDesignChoice();
17298
+ /** Checks explicit decorative annotations; transitions are budgeted separately. */
17299
+ function hasDecorativeAnnotation(context) {
17300
+ return context?.decorativeIntent === "supporting" || context?.decorativeIntent === "feature";
17233
17301
  }
17234
- function buildBoundaryPin(previousBlockId, nextBlockId, choice) {
17235
- return {
17236
- boundaryId: makeBoundaryId(previousBlockId, nextBlockId),
17237
- previousBlockId,
17238
- nextBlockId,
17239
- choice
17240
- };
17302
+ /** Checks whether a block annotation says media contributes visual weight. */
17303
+ function hasMediaAnnotation(context) {
17304
+ return context?.mediaIntent === "supporting" || context?.mediaIntent === "dominant";
17241
17305
  }
17242
- function reducePageDesignIntentForStructureEvent(intent, event) {
17243
- switch (event.kind) {
17244
- case "block-added": return {
17245
- intent,
17246
- issues: []
17247
- };
17248
- case "block-removed": return removeBlockFromPageDesignIntent(intent, event.blockId);
17249
- case "blocks-reordered": return pruneBoundaryPinsForOrder(intent, event.orderedBlockIds);
17250
- case "block-kind-changed": return changeBlockKindInPageDesignIntent(intent, event);
17251
- case "block-duplicated": return duplicateBlockInPageDesignIntent(intent, event);
17252
- default: return assertNever$1(event, "Unhandled page design structure event");
17306
+ function validateCompositionBudget(input) {
17307
+ const { budget, policy, resolved } = input;
17308
+ const totalBlocks = resolved.blocks.length;
17309
+ const knownBlockIds = new Set(resolved.blocks.map((block) => block.blockId));
17310
+ const blockContexts = input.blockContexts ?? [];
17311
+ const unknownContextBlockIds = blockContexts.filter((context) => !knownBlockIds.has(context.blockId)).map((context) => context.blockId);
17312
+ const knownBlockContexts = blockContexts.filter((context) => knownBlockIds.has(context.blockId));
17313
+ const contextsByBlockId = new Map(knownBlockContexts.map((context) => [context.blockId, context]));
17314
+ const issues = [];
17315
+ if (unknownContextBlockIds.length > 0) {
17316
+ issues.push({
17317
+ kind: "unknown-composition-context-block",
17318
+ blockIds: unknownContextBlockIds,
17319
+ ...issueMetadata(policy, [])
17320
+ });
17253
17321
  }
17254
- }
17255
- function reducePageDesignIntentForUserAction(intent, action) {
17256
- switch (action.kind) {
17257
- case "set-page-flow": return {
17258
- ...intent,
17259
- pageFlowPresetId: action.pageFlowPresetId
17260
- };
17261
- case "set-block-look": return setBlockLookChoice(intent, action.block, action.choice);
17262
- case "set-boundary": return setBoundaryChoice(intent, action.previousBlockId, action.nextBlockId, action.choice);
17263
- default: return assertNever$1(action, "Unhandled page design user action");
17322
+ const accentBlockIds = resolved.blocks.filter((block) => block.appliedSectionSurface === "accent-band" || block.appliedSectionSurface === "brand-depth-band").map((block) => block.blockId);
17323
+ const accentIssue = issueWhenRatioExceeded({
17324
+ kind: "accent-density-exceeded",
17325
+ policy,
17326
+ budget: budget.accentDensity,
17327
+ blockIds: accentBlockIds,
17328
+ totalBlocks,
17329
+ allowedRatio: compositionDensityRatioLimits[budget.accentDensity]
17330
+ });
17331
+ if (accentIssue) {
17332
+ issues.push(accentIssue);
17264
17333
  }
17265
- }
17266
- function remapPageDesignIntentForDuplicate(intent, input) {
17267
- const targetBySourceId = new Map(input.blockMappings.map((mapping) => [mapping.sourceBlockId, {
17268
- blockId: mapping.targetBlockId,
17269
- blockKind: mapping.targetBlockKind
17270
- }]));
17271
- const targetBlockOrder = input.blockMappings.map((mapping) => mapping.targetBlockId);
17272
- const adjacentTargetPairs = adjacentPairKeySet(targetBlockOrder);
17273
- const blockLookPins = intent.blockLookPins.flatMap((pin) => {
17274
- const target = targetBySourceId.get(pin.blockId);
17275
- return target ? [{
17276
- blockId: target.blockId,
17277
- blockKind: target.blockKind,
17278
- choice: pin.choice
17279
- }] : [];
17334
+ const panelBlockIds = resolved.blocks.filter(hasPanelFrameOrCardSurface).map((block) => block.blockId);
17335
+ const panelIssue = issueWhenRatioExceeded({
17336
+ kind: "panel-density-exceeded",
17337
+ policy,
17338
+ budget: budget.panelDensity,
17339
+ blockIds: panelBlockIds,
17340
+ totalBlocks,
17341
+ allowedRatio: compositionDensityRatioLimits[budget.panelDensity]
17280
17342
  });
17281
- const boundaryPins = intent.boundaryPins.flatMap((pin) => {
17282
- const targetPrevious = targetBySourceId.get(pin.previousBlockId);
17283
- const targetNext = targetBySourceId.get(pin.nextBlockId);
17284
- if (!targetPrevious || !targetNext) {
17285
- return [];
17286
- }
17287
- if (!adjacentTargetPairs.has(boundaryKey(targetPrevious.blockId, targetNext.blockId))) {
17288
- return [];
17289
- }
17290
- return [buildBoundaryPin(targetPrevious.blockId, targetNext.blockId, pin.choice)];
17343
+ if (panelIssue) {
17344
+ issues.push(panelIssue);
17345
+ }
17346
+ const frameByBlockId = new Map(input.resolved.blocks.map((block) => [block.blockId, block.appliedContentFrame.kind]));
17347
+ const transitionBlockIds = input.joins.filter((join) => {
17348
+ const previous = frameByBlockId.get(join.previousBlockId);
17349
+ const next = frameByBlockId.get(join.nextBlockId);
17350
+ return previous !== undefined && next !== undefined && frameNotShownReason(previous, next) === null;
17351
+ }).map((join) => join.previousBlockId);
17352
+ const transitionIssue = issueWhenRatioExceeded({
17353
+ kind: "transition-mix-exceeded",
17354
+ policy,
17355
+ budget: budget.transitionMix,
17356
+ blockIds: transitionBlockIds,
17357
+ totalBlocks,
17358
+ allowedRatio: compositionTransitionMixRatioLimits[budget.transitionMix]
17359
+ });
17360
+ if (transitionIssue) {
17361
+ issues.push(transitionIssue);
17362
+ }
17363
+ const ctaContexts = knownBlockContexts.filter((context) => (context.primaryCtaCount ?? 0) > 0);
17364
+ const actualPrimaryCtaCount = ctaContexts.reduce((sum, context) => sum + (context.primaryCtaCount ?? 0), 0);
17365
+ if (actualPrimaryCtaCount > budget.primaryCtaLimit) {
17366
+ issues.push({
17367
+ kind: "primary-cta-limit-exceeded",
17368
+ actualCount: actualPrimaryCtaCount,
17369
+ allowedCount: budget.primaryCtaLimit,
17370
+ blockIds: ctaContexts.map((context) => context.blockId),
17371
+ ...issueMetadata(policy, ["reduce-primary-ctas"])
17372
+ });
17373
+ }
17374
+ const mediaBlockIds = resolved.blocks.filter((block) => hasMediaAnnotation(contextsByBlockId.get(block.blockId))).map((block) => block.blockId);
17375
+ const mediaIssue = issueWhenRatioExceeded({
17376
+ kind: "media-density-exceeded",
17377
+ policy,
17378
+ budget: budget.mediaDensity,
17379
+ blockIds: mediaBlockIds,
17380
+ totalBlocks,
17381
+ allowedRatio: compositionDensityRatioLimits[budget.mediaDensity]
17382
+ });
17383
+ if (mediaIssue) {
17384
+ issues.push(mediaIssue);
17385
+ }
17386
+ const decorativeBlockIds = resolved.blocks.filter((block) => hasDecorativeAnnotation(contextsByBlockId.get(block.blockId))).map((block) => block.blockId);
17387
+ const decorativeIssue = issueWhenRatioExceeded({
17388
+ kind: "decorative-repetition-exceeded",
17389
+ policy,
17390
+ budget: budget.decorativeRepetition,
17391
+ blockIds: decorativeBlockIds,
17392
+ totalBlocks,
17393
+ allowedRatio: compositionDensityRatioLimits[budget.decorativeRepetition]
17291
17394
  });
17395
+ if (decorativeIssue) {
17396
+ issues.push(decorativeIssue);
17397
+ }
17398
+ return issues;
17399
+ }
17400
+
17401
+ //#endregion
17402
+ //#region ../theme-core/src/site-styles/compilationPlan.ts
17403
+ const defaultHeaderBrandPreferences = {
17404
+ brandDisplay: "auto",
17405
+ logoSize: "auto"
17406
+ };
17407
+ function headerBrandPreferencesFromTheme(theme) {
17292
17408
  return {
17293
- intent: {
17294
- ...intent,
17295
- blockLookPins,
17296
- boundaryPins
17409
+ brandDisplay: theme?.header.logo?.brandDisplay ?? defaultHeaderBrandPreferences.brandDisplay,
17410
+ logoSize: theme?.header.logo?.size ?? defaultHeaderBrandPreferences.logoSize
17411
+ };
17412
+ }
17413
+ function replaceSiteStyleCompilationPlan(input) {
17414
+ return {
17415
+ kind: "replace-site-style",
17416
+ buttons: {
17417
+ kind: "recompile",
17418
+ selection: input.buttonStyleSelection ?? null,
17419
+ reason: "site-style-replacement"
17297
17420
  },
17298
- issues: []
17421
+ header: {
17422
+ kind: "recompile",
17423
+ selection: input.headerSelection,
17424
+ brandPresentation: input.headerLogo === undefined ? { kind: "use-compiled-defaults" } : {
17425
+ kind: "apply-logo-context",
17426
+ logo: input.headerLogo
17427
+ }
17428
+ },
17429
+ footerLookId: input.footerLookId ?? null
17299
17430
  };
17300
17431
  }
17301
- function setBlockLookChoice(intent, block, choice) {
17302
- const withoutBlock = intent.blockLookPins.filter((pin) => pin.blockId !== block.blockId);
17303
- if (choice.kind === "auto") {
17304
- return {
17305
- ...intent,
17306
- blockLookPins: withoutBlock
17307
- };
17432
+ /**
17433
+ * Writes the brand display / logo size preference onto the header's logo style.
17434
+ *
17435
+ * Moved here from preview-next in #1318 Phase 4a so the header preference
17436
+ * pipeline can be composed once, in theme-core, rather than half in the client.
17437
+ */
17438
+ function withHeaderBrandThemePreferences(theme, preferences) {
17439
+ const currentLogo = theme.header.logo;
17440
+ if (!currentLogo && preferences.brandDisplay === defaultHeaderBrandPreferences.brandDisplay && preferences.logoSize === defaultHeaderBrandPreferences.logoSize) {
17441
+ return theme;
17308
17442
  }
17309
17443
  return {
17310
- ...intent,
17311
- blockLookPins: [...withoutBlock, {
17312
- blockId: block.blockId,
17313
- blockKind: block.blockKind,
17314
- choice
17315
- }]
17444
+ ...theme,
17445
+ header: {
17446
+ ...theme.header,
17447
+ logo: {
17448
+ fontFamily: currentLogo?.fontFamily ?? "heading",
17449
+ letterSpacing: currentLogo?.letterSpacing ?? "normal",
17450
+ gradient: currentLogo?.gradient ?? false,
17451
+ ...currentLogo ?? {},
17452
+ brandDisplay: preferences.brandDisplay,
17453
+ size: preferences.logoSize
17454
+ }
17455
+ }
17316
17456
  };
17317
17457
  }
17318
- function setBoundaryChoice(intent, previousBlockId, nextBlockId, choice) {
17319
- const withoutBoundary = intent.boundaryPins.filter((pin) => pin.previousBlockId !== previousBlockId || pin.nextBlockId !== nextBlockId);
17320
- if (choice.kind === "auto") {
17458
+
17459
+ //#endregion
17460
+ //#region ../theme-core/src/site-styles/schemaVersionedJson.ts
17461
+ function parseSchemaVersionedJson(input) {
17462
+ if (input.value == null) {
17463
+ return { kind: "absent" };
17464
+ }
17465
+ if (typeof input.value === "object" && input.value !== null && "schemaVersion" in input.value && input.value.schemaVersion !== input.expectedSchemaVersion) {
17321
17466
  return {
17322
- ...intent,
17323
- boundaryPins: withoutBoundary
17467
+ kind: "invalid",
17468
+ issue: {
17469
+ kind: "unsupported-schema-version",
17470
+ message: input.unsupportedSchemaVersionMessage
17471
+ }
17472
+ };
17473
+ }
17474
+ const parsed = input.schema.safeParse(input.value);
17475
+ if (!parsed.success) {
17476
+ const firstIssue = parsed.error.issues[0];
17477
+ return {
17478
+ kind: "invalid",
17479
+ issue: {
17480
+ kind: "invalid-json-shape",
17481
+ message: firstIssue?.message ?? input.invalidShapeMessage
17482
+ }
17324
17483
  };
17325
17484
  }
17326
17485
  return {
17327
- ...intent,
17328
- boundaryPins: [...withoutBoundary, buildBoundaryPin(previousBlockId, nextBlockId, choice)]
17329
- };
17330
- }
17331
- function removeBlockFromPageDesignIntent(intent, blockId) {
17332
- const removedLookIssues = intent.blockLookPins.flatMap((pin) => pin.blockId === blockId ? [{
17333
- kind: "removed-look-pin-for-deleted-block",
17334
- blockId,
17335
- presetId: pin.choice.value,
17336
- feedback: "debug"
17337
- }] : []);
17338
- const removedBoundaryIssues = intent.boundaryPins.flatMap((pin) => pin.previousBlockId === blockId || pin.nextBlockId === blockId ? [{
17339
- kind: "removed-boundary-pin-for-broken-adjacency",
17340
- boundaryId: pin.boundaryId,
17341
- previousBlockId: pin.previousBlockId,
17342
- nextBlockId: pin.nextBlockId,
17343
- presetId: boundaryIssuePresetId(pin.choice.value),
17344
- feedback: "debug"
17345
- }] : []);
17346
- return {
17347
- intent: {
17348
- ...intent,
17349
- blockLookPins: intent.blockLookPins.filter((pin) => pin.blockId !== blockId),
17350
- boundaryPins: intent.boundaryPins.filter((pin) => pin.previousBlockId !== blockId && pin.nextBlockId !== blockId)
17351
- },
17352
- issues: [...removedLookIssues, ...removedBoundaryIssues]
17353
- };
17354
- }
17355
- function pruneBoundaryPinsForOrder(intent, orderedBlockIds) {
17356
- const adjacentPairs = adjacentPairKeySet(orderedBlockIds);
17357
- const removedBoundaryIssues = intent.boundaryPins.flatMap((pin) => adjacentPairs.has(boundaryKey(pin.previousBlockId, pin.nextBlockId)) ? [] : [{
17358
- kind: "removed-boundary-pin-for-broken-adjacency",
17359
- boundaryId: pin.boundaryId,
17360
- previousBlockId: pin.previousBlockId,
17361
- nextBlockId: pin.nextBlockId,
17362
- presetId: boundaryIssuePresetId(pin.choice.value),
17363
- feedback: "debug"
17364
- }]);
17365
- return {
17366
- intent: {
17367
- ...intent,
17368
- boundaryPins: intent.boundaryPins.filter((pin) => adjacentPairs.has(boundaryKey(pin.previousBlockId, pin.nextBlockId)))
17369
- },
17370
- issues: removedBoundaryIssues
17486
+ kind: "parsed",
17487
+ value: parsed.data
17371
17488
  };
17372
17489
  }
17490
+
17491
+ //#endregion
17492
+ //#region ../theme-core/src/site-styles/pageDesignIntent.ts
17493
+ const pageDesignIntentSchemaVersion = 2;
17494
+ const legacyPageDesignIntentSchemaVersion = 1;
17495
+ const unknownBoundaryOptionId = asBoundaryOptionId("break:unknown");
17373
17496
  /**
17374
- * A block-kind change updates the pin's current block kind so future resolution sees
17375
- * the current tree. If the pinned Look is incompatible, the pin remains stored and
17376
- * the resolver must render a safe auto fallback while the user-facing issue is open.
17497
+ * Site style is site-owned authority. Page design snapshots retain pins and
17498
+ * flow choices, but must not keep resolving against a superseded site style.
17377
17499
  */
17378
- function changeBlockKindInPageDesignIntent(intent, event) {
17379
- const blockLookPins = intent.blockLookPins.map((pin) => pin.blockId === event.blockId ? {
17380
- ...pin,
17381
- blockKind: event.nextBlockKind
17382
- } : pin);
17383
- const changed = intent.blockLookPins.find((pin) => pin.blockId === event.blockId);
17384
- const incompatiblePinnedLook = changed && !event.compatiblePinnedLookPresetIds.includes(changed.choice.value) ? changed : null;
17385
- return {
17386
- intent: {
17387
- ...intent,
17388
- blockLookPins
17389
- },
17390
- issues: incompatiblePinnedLook ? [{
17391
- kind: "pinned-look-incompatible-with-block-kind",
17392
- blockId: event.blockId,
17393
- presetId: incompatiblePinnedLook.choice.value,
17394
- previousBlockKind: incompatiblePinnedLook.blockKind,
17395
- nextBlockKind: event.nextBlockKind,
17396
- feedback: "user-facing"
17397
- }] : []
17500
+ function rebasePageDesignIntentToSiteStyle(intent, siteStyleId) {
17501
+ return intent.siteStyleId === siteStyleId ? intent : {
17502
+ ...intent,
17503
+ siteStyleId
17398
17504
  };
17399
17505
  }
17400
- function boundaryIssuePresetId(treatment) {
17401
- if (typeof treatment === "string") return treatment;
17402
- return boundaryOptionIdForTreatment(treatment) ?? unknownBoundaryOptionId;
17403
- }
17506
+ const pageDesignUserPinnedAppearanceChoiceSchema = z.object({
17507
+ kind: z.literal("user-pinned"),
17508
+ value: z.string().min(1).transform(asAppearancePresetId)
17509
+ }).strict();
17510
+ const pageDesignUserPinnedBoundaryChoiceSchema = z.object({
17511
+ kind: z.literal("user-pinned"),
17512
+ value: z.string().min(1).transform(asBoundaryOptionId)
17513
+ }).strict();
17514
+ const pageDesignBoundaryTreatmentSchema = z.discriminatedUnion("kind", [
17515
+ z.object({ kind: z.literal("none") }).strict(),
17516
+ z.object({
17517
+ kind: z.literal("soft-fade"),
17518
+ height: z.enum([
17519
+ "compact",
17520
+ "medium",
17521
+ "large",
17522
+ "expansive"
17523
+ ]),
17524
+ curve: z.enum([
17525
+ "soft",
17526
+ "spacious",
17527
+ "expansive"
17528
+ ])
17529
+ }).strict(),
17530
+ z.object({
17531
+ kind: z.literal("wave-edge"),
17532
+ height: z.enum([
17533
+ "compact",
17534
+ "medium",
17535
+ "large"
17536
+ ]),
17537
+ amplitude: z.enum([
17538
+ "subtle",
17539
+ "balanced",
17540
+ "bold"
17541
+ ]),
17542
+ rhythm: z.enum(["single", "double"]),
17543
+ symmetry: z.enum([
17544
+ "symmetrical",
17545
+ "asymmetrical",
17546
+ "drift"
17547
+ ])
17548
+ }).strict(),
17549
+ z.object({
17550
+ kind: z.literal("angle-edge"),
17551
+ height: z.enum([
17552
+ "compact",
17553
+ "medium",
17554
+ "large"
17555
+ ]),
17556
+ slope: z.enum([
17557
+ "subtle",
17558
+ "balanced",
17559
+ "steep"
17560
+ ]),
17561
+ direction: z.enum(["forward", "reverse"])
17562
+ }).strict(),
17563
+ z.object({
17564
+ kind: z.literal("legacy-unsupported"),
17565
+ boundaryOptionId: z.string().min(1).transform(asBoundaryOptionId)
17566
+ }).strict()
17567
+ ]);
17568
+ const pageDesignUserPinnedBoundaryTreatmentChoiceSchema = z.object({
17569
+ kind: z.literal("user-pinned"),
17570
+ value: pageDesignBoundaryTreatmentSchema
17571
+ }).strict();
17572
+ const pageDesignBlockLookPinSchema = z.object({
17573
+ blockId: z.string().min(1).transform(asDesignBlockId),
17574
+ blockKind: z.string().min(1),
17575
+ choice: pageDesignUserPinnedAppearanceChoiceSchema
17576
+ }).strict();
17577
+ const pageDesignBoundaryPinSchema = z.object({
17578
+ boundaryId: z.string().min(1).transform(asDesignBoundaryId),
17579
+ previousBlockId: z.string().min(1).transform(asDesignBlockId),
17580
+ nextBlockId: z.string().min(1).transform(asDesignBlockId),
17581
+ choice: pageDesignUserPinnedBoundaryTreatmentChoiceSchema
17582
+ }).strict().superRefine(validateBoundaryPinIdMatchesBlocks);
17583
+ const legacyPageDesignBoundaryPinSchema = z.object({
17584
+ boundaryId: z.string().min(1).transform(asDesignBoundaryId),
17585
+ previousBlockId: z.string().min(1).transform(asDesignBlockId),
17586
+ nextBlockId: z.string().min(1).transform(asDesignBlockId),
17587
+ choice: pageDesignUserPinnedBoundaryChoiceSchema
17588
+ }).strict().superRefine(validateBoundaryPinIdMatchesBlocks);
17404
17589
  /**
17405
- * Ordinary block duplication does not copy boundary pins. Boundary pins represent
17406
- * exact adjacent pairs, so duplicated blocks start with automatic surrounding breaks.
17590
+ * Persisted editor intent stores only user-pinned block Looks and boundary choices.
17591
+ * Missing pins mean `auto`; block-kind/preset compatibility is enforced by resolution,
17592
+ * not by this JSON parse boundary.
17407
17593
  */
17408
- function duplicateBlockInPageDesignIntent(intent, event) {
17409
- const sourcePin = intent.blockLookPins.find((pin) => pin.blockId === event.sourceBlockId);
17410
- if (!event.copyPinnedLook) {
17411
- return {
17412
- intent,
17413
- issues: []
17414
- };
17594
+ const pageDesignIntentV1Schema = z.object({
17595
+ schemaVersion: z.literal(legacyPageDesignIntentSchemaVersion),
17596
+ siteStyleId: z.string().min(1).transform(asSiteStyleId),
17597
+ pageFlowPresetId: z.string().min(1).transform(asPageFlowPresetId),
17598
+ blockLookPins: z.array(pageDesignBlockLookPinSchema),
17599
+ boundaryPins: z.array(legacyPageDesignBoundaryPinSchema)
17600
+ }).strict().superRefine(validateUniquePageDesignPins);
17601
+ const pageDesignIntentV2Schema = z.object({
17602
+ schemaVersion: z.literal(pageDesignIntentSchemaVersion),
17603
+ siteStyleId: z.string().min(1).transform(asSiteStyleId),
17604
+ pageFlowPresetId: z.string().min(1).transform(asPageFlowPresetId),
17605
+ blockLookPins: z.array(pageDesignBlockLookPinSchema),
17606
+ boundaryPins: z.array(pageDesignBoundaryPinSchema)
17607
+ }).strict().superRefine(validateUniquePageDesignPins);
17608
+ function parsePageDesignIntent(value, options = {}) {
17609
+ if (isLegacyPageDesignIntentCandidate(value)) {
17610
+ const legacyParsed = parseSchemaVersionedJson({
17611
+ value,
17612
+ schema: pageDesignIntentV1Schema,
17613
+ expectedSchemaVersion: legacyPageDesignIntentSchemaVersion,
17614
+ unsupportedSchemaVersionMessage: options.unsupportedSchemaVersionMessage ?? "Unsupported Theme V2 page design intent schemaVersion.",
17615
+ invalidShapeMessage: options.invalidShapeMessage ?? "Invalid Theme V2 page design intent shape."
17616
+ });
17617
+ switch (legacyParsed.kind) {
17618
+ case "absent": return legacyParsed;
17619
+ case "parsed": return {
17620
+ kind: "parsed",
17621
+ intent: normalizeLegacyPageDesignIntent(legacyParsed.value)
17622
+ };
17623
+ case "invalid": return legacyParsed;
17624
+ default: return assertNever$1(legacyParsed, "Unhandled legacy page design intent JSON parse result");
17625
+ }
17415
17626
  }
17416
- if (!sourcePin) {
17417
- return {
17418
- intent,
17419
- issues: [{
17420
- kind: "missing-look-pin-to-copy-on-duplicate",
17421
- sourceBlockId: event.sourceBlockId,
17422
- duplicatedBlockId: event.block.blockId,
17423
- feedback: "debug"
17424
- }]
17627
+ const parsed = parseSchemaVersionedJson({
17628
+ value,
17629
+ schema: pageDesignIntentV2Schema,
17630
+ expectedSchemaVersion: pageDesignIntentSchemaVersion,
17631
+ unsupportedSchemaVersionMessage: options.unsupportedSchemaVersionMessage ?? "Unsupported Theme V2 page design intent schemaVersion.",
17632
+ invalidShapeMessage: options.invalidShapeMessage ?? "Invalid Theme V2 page design intent shape."
17633
+ });
17634
+ switch (parsed.kind) {
17635
+ case "absent": return parsed;
17636
+ case "parsed": return {
17637
+ kind: "parsed",
17638
+ intent: parsed.value
17425
17639
  };
17640
+ case "invalid": return parsed;
17641
+ default: return assertNever$1(parsed, "Unhandled page design intent JSON parse result");
17426
17642
  }
17643
+ }
17644
+ function isLegacyPageDesignIntentCandidate(value) {
17645
+ return typeof value === "object" && value !== null && "schemaVersion" in value && value.schemaVersion === legacyPageDesignIntentSchemaVersion;
17646
+ }
17647
+ function normalizeLegacyPageDesignIntent(intent) {
17427
17648
  return {
17428
- intent: {
17429
- ...intent,
17430
- blockLookPins: [...intent.blockLookPins, {
17431
- blockId: event.block.blockId,
17432
- blockKind: event.block.blockKind,
17433
- choice: sourcePin.choice
17434
- }]
17435
- },
17436
- issues: []
17649
+ schemaVersion: pageDesignIntentSchemaVersion,
17650
+ siteStyleId: intent.siteStyleId,
17651
+ pageFlowPresetId: intent.pageFlowPresetId,
17652
+ blockLookPins: intent.blockLookPins,
17653
+ boundaryPins: intent.boundaryPins.map((pin) => ({
17654
+ boundaryId: pin.boundaryId,
17655
+ previousBlockId: pin.previousBlockId,
17656
+ nextBlockId: pin.nextBlockId,
17657
+ choice: userPinnedDesignChoice(boundaryTreatmentForBoundaryOptionId(pin.choice.value))
17658
+ }))
17437
17659
  };
17438
17660
  }
17439
- function adjacentPairKeySet(orderedBlockIds) {
17440
- const pairs = new Set();
17441
- const [first, ...rest] = orderedBlockIds;
17442
- if (!first) {
17443
- return pairs;
17444
- }
17445
- let previousBlockId = first;
17446
- for (const nextBlockId of rest) {
17447
- pairs.add(boundaryKey(previousBlockId, nextBlockId));
17448
- previousBlockId = nextBlockId;
17449
- }
17450
- return pairs;
17661
+ function autoDesignChoice() {
17662
+ return { kind: "auto" };
17451
17663
  }
17452
- function addDuplicateStringIssues(ctx, input) {
17664
+ function userPinnedDesignChoice(value) {
17665
+ return {
17666
+ kind: "user-pinned",
17667
+ value
17668
+ };
17669
+ }
17670
+ function resolvePageDesignBlockLookChoice(intent, blockId) {
17671
+ return intent.blockLookPins.find((pin) => pin.blockId === blockId)?.choice ?? autoDesignChoice();
17672
+ }
17673
+ function resolvePageDesignBoundaryChoice(intent, previousBlockId, nextBlockId) {
17674
+ return intent.boundaryPins.find((pin) => pin.previousBlockId === previousBlockId && pin.nextBlockId === nextBlockId)?.choice ?? autoDesignChoice();
17675
+ }
17676
+ function buildBoundaryPin(previousBlockId, nextBlockId, choice) {
17677
+ return {
17678
+ boundaryId: makeBoundaryId(previousBlockId, nextBlockId),
17679
+ previousBlockId,
17680
+ nextBlockId,
17681
+ choice
17682
+ };
17683
+ }
17684
+ function reducePageDesignIntentForStructureEvent(intent, event) {
17685
+ switch (event.kind) {
17686
+ case "block-added": return {
17687
+ intent,
17688
+ issues: []
17689
+ };
17690
+ case "block-removed": return removeBlockFromPageDesignIntent(intent, event.blockId);
17691
+ case "blocks-reordered": return pruneBoundaryPinsForOrder(intent, event.orderedBlockIds);
17692
+ case "block-kind-changed": return changeBlockKindInPageDesignIntent(intent, event);
17693
+ case "block-duplicated": return duplicateBlockInPageDesignIntent(intent, event);
17694
+ default: return assertNever$1(event, "Unhandled page design structure event");
17695
+ }
17696
+ }
17697
+ function reducePageDesignIntentForUserAction(intent, action) {
17698
+ switch (action.kind) {
17699
+ case "set-page-flow": return {
17700
+ ...intent,
17701
+ pageFlowPresetId: action.pageFlowPresetId
17702
+ };
17703
+ case "set-block-look": return setBlockLookChoice(intent, action.block, action.choice);
17704
+ case "set-boundary": return setBoundaryChoice(intent, action.previousBlockId, action.nextBlockId, action.choice);
17705
+ default: return assertNever$1(action, "Unhandled page design user action");
17706
+ }
17707
+ }
17708
+ function remapPageDesignIntentForDuplicate(intent, input) {
17709
+ const targetBySourceId = new Map(input.blockMappings.map((mapping) => [mapping.sourceBlockId, {
17710
+ blockId: mapping.targetBlockId,
17711
+ blockKind: mapping.targetBlockKind
17712
+ }]));
17713
+ const targetBlockOrder = input.blockMappings.map((mapping) => mapping.targetBlockId);
17714
+ const adjacentTargetPairs = adjacentPairKeySet(targetBlockOrder);
17715
+ const blockLookPins = intent.blockLookPins.flatMap((pin) => {
17716
+ const target = targetBySourceId.get(pin.blockId);
17717
+ return target ? [{
17718
+ blockId: target.blockId,
17719
+ blockKind: target.blockKind,
17720
+ choice: pin.choice
17721
+ }] : [];
17722
+ });
17723
+ const boundaryPins = intent.boundaryPins.flatMap((pin) => {
17724
+ const targetPrevious = targetBySourceId.get(pin.previousBlockId);
17725
+ const targetNext = targetBySourceId.get(pin.nextBlockId);
17726
+ if (!targetPrevious || !targetNext) {
17727
+ return [];
17728
+ }
17729
+ if (!adjacentTargetPairs.has(makeBoundaryId(targetPrevious.blockId, targetNext.blockId))) {
17730
+ return [];
17731
+ }
17732
+ return [buildBoundaryPin(targetPrevious.blockId, targetNext.blockId, pin.choice)];
17733
+ });
17734
+ return {
17735
+ intent: {
17736
+ ...intent,
17737
+ blockLookPins,
17738
+ boundaryPins
17739
+ },
17740
+ issues: []
17741
+ };
17742
+ }
17743
+ function setBlockLookChoice(intent, block, choice) {
17744
+ const withoutBlock = intent.blockLookPins.filter((pin) => pin.blockId !== block.blockId);
17745
+ if (choice.kind === "auto") {
17746
+ return {
17747
+ ...intent,
17748
+ blockLookPins: withoutBlock
17749
+ };
17750
+ }
17751
+ return {
17752
+ ...intent,
17753
+ blockLookPins: [...withoutBlock, {
17754
+ blockId: block.blockId,
17755
+ blockKind: block.blockKind,
17756
+ choice
17757
+ }]
17758
+ };
17759
+ }
17760
+ function setBoundaryChoice(intent, previousBlockId, nextBlockId, choice) {
17761
+ const withoutBoundary = intent.boundaryPins.filter((pin) => pin.previousBlockId !== previousBlockId || pin.nextBlockId !== nextBlockId);
17762
+ if (choice.kind === "auto") {
17763
+ return {
17764
+ ...intent,
17765
+ boundaryPins: withoutBoundary
17766
+ };
17767
+ }
17768
+ return {
17769
+ ...intent,
17770
+ boundaryPins: [...withoutBoundary, buildBoundaryPin(previousBlockId, nextBlockId, choice)]
17771
+ };
17772
+ }
17773
+ function removeBlockFromPageDesignIntent(intent, blockId) {
17774
+ const removedLookIssues = intent.blockLookPins.flatMap((pin) => pin.blockId === blockId ? [{
17775
+ kind: "removed-look-pin-for-deleted-block",
17776
+ blockId,
17777
+ presetId: pin.choice.value,
17778
+ feedback: "debug"
17779
+ }] : []);
17780
+ const removedBoundaryIssues = intent.boundaryPins.flatMap((pin) => pin.previousBlockId === blockId || pin.nextBlockId === blockId ? [{
17781
+ kind: "removed-boundary-pin-for-broken-adjacency",
17782
+ boundaryId: pin.boundaryId,
17783
+ previousBlockId: pin.previousBlockId,
17784
+ nextBlockId: pin.nextBlockId,
17785
+ presetId: boundaryIssuePresetId(pin.choice.value),
17786
+ feedback: "debug"
17787
+ }] : []);
17788
+ return {
17789
+ intent: {
17790
+ ...intent,
17791
+ blockLookPins: intent.blockLookPins.filter((pin) => pin.blockId !== blockId),
17792
+ boundaryPins: intent.boundaryPins.filter((pin) => pin.previousBlockId !== blockId && pin.nextBlockId !== blockId)
17793
+ },
17794
+ issues: [...removedLookIssues, ...removedBoundaryIssues]
17795
+ };
17796
+ }
17797
+ function pruneBoundaryPinsForOrder(intent, orderedBlockIds) {
17798
+ const adjacentPairs = adjacentPairKeySet(orderedBlockIds);
17799
+ const removedBoundaryIssues = intent.boundaryPins.flatMap((pin) => adjacentPairs.has(makeBoundaryId(pin.previousBlockId, pin.nextBlockId)) ? [] : [{
17800
+ kind: "removed-boundary-pin-for-broken-adjacency",
17801
+ boundaryId: pin.boundaryId,
17802
+ previousBlockId: pin.previousBlockId,
17803
+ nextBlockId: pin.nextBlockId,
17804
+ presetId: boundaryIssuePresetId(pin.choice.value),
17805
+ feedback: "debug"
17806
+ }]);
17807
+ return {
17808
+ intent: {
17809
+ ...intent,
17810
+ boundaryPins: intent.boundaryPins.filter((pin) => adjacentPairs.has(makeBoundaryId(pin.previousBlockId, pin.nextBlockId)))
17811
+ },
17812
+ issues: removedBoundaryIssues
17813
+ };
17814
+ }
17815
+ /**
17816
+ * A block-kind change updates the pin's current block kind so future resolution sees
17817
+ * the current tree. If the pinned Look is incompatible, the pin remains stored and
17818
+ * the resolver must render a safe auto fallback while the user-facing issue is open.
17819
+ */
17820
+ function changeBlockKindInPageDesignIntent(intent, event) {
17821
+ const blockLookPins = intent.blockLookPins.map((pin) => pin.blockId === event.blockId ? {
17822
+ ...pin,
17823
+ blockKind: event.nextBlockKind
17824
+ } : pin);
17825
+ const changed = intent.blockLookPins.find((pin) => pin.blockId === event.blockId);
17826
+ const incompatiblePinnedLook = changed && !event.compatiblePinnedLookPresetIds.includes(changed.choice.value) ? changed : null;
17827
+ return {
17828
+ intent: {
17829
+ ...intent,
17830
+ blockLookPins
17831
+ },
17832
+ issues: incompatiblePinnedLook ? [{
17833
+ kind: "pinned-look-incompatible-with-block-kind",
17834
+ blockId: event.blockId,
17835
+ presetId: incompatiblePinnedLook.choice.value,
17836
+ previousBlockKind: incompatiblePinnedLook.blockKind,
17837
+ nextBlockKind: event.nextBlockKind,
17838
+ feedback: "user-facing"
17839
+ }] : []
17840
+ };
17841
+ }
17842
+ function boundaryIssuePresetId(treatment) {
17843
+ if (typeof treatment === "string") return treatment;
17844
+ return boundaryOptionIdForTreatment(treatment) ?? unknownBoundaryOptionId;
17845
+ }
17846
+ /**
17847
+ * Ordinary block duplication does not copy boundary pins. Boundary pins represent
17848
+ * exact adjacent pairs, so duplicated blocks start with automatic surrounding breaks.
17849
+ */
17850
+ function duplicateBlockInPageDesignIntent(intent, event) {
17851
+ const sourcePin = intent.blockLookPins.find((pin) => pin.blockId === event.sourceBlockId);
17852
+ if (!event.copyPinnedLook) {
17853
+ return {
17854
+ intent,
17855
+ issues: []
17856
+ };
17857
+ }
17858
+ if (!sourcePin) {
17859
+ return {
17860
+ intent,
17861
+ issues: [{
17862
+ kind: "missing-look-pin-to-copy-on-duplicate",
17863
+ sourceBlockId: event.sourceBlockId,
17864
+ duplicatedBlockId: event.block.blockId,
17865
+ feedback: "debug"
17866
+ }]
17867
+ };
17868
+ }
17869
+ return {
17870
+ intent: {
17871
+ ...intent,
17872
+ blockLookPins: [...intent.blockLookPins, {
17873
+ blockId: event.block.blockId,
17874
+ blockKind: event.block.blockKind,
17875
+ choice: sourcePin.choice
17876
+ }]
17877
+ },
17878
+ issues: []
17879
+ };
17880
+ }
17881
+ function adjacentPairKeySet(orderedBlockIds) {
17882
+ const pairs = new Set();
17883
+ const [first, ...rest] = orderedBlockIds;
17884
+ if (!first) {
17885
+ return pairs;
17886
+ }
17887
+ let previousBlockId = first;
17888
+ for (const nextBlockId of rest) {
17889
+ pairs.add(makeBoundaryId(previousBlockId, nextBlockId));
17890
+ previousBlockId = nextBlockId;
17891
+ }
17892
+ return pairs;
17893
+ }
17894
+ function addDuplicateStringIssues(ctx, input) {
17453
17895
  const seen = new Set();
17454
17896
  const duplicates = new Set();
17455
17897
  for (const value of input.values) {
@@ -17483,14 +17925,11 @@ function validateUniquePageDesignPins(intent, ctx) {
17483
17925
  label: "block Look pin"
17484
17926
  });
17485
17927
  addDuplicateStringIssues(ctx, {
17486
- values: intent.boundaryPins.map((pin) => boundaryKey(pin.previousBlockId, pin.nextBlockId)),
17928
+ values: intent.boundaryPins.map((pin) => makeBoundaryId(pin.previousBlockId, pin.nextBlockId)),
17487
17929
  path: ["boundaryPins"],
17488
17930
  label: "boundary pin"
17489
17931
  });
17490
17932
  }
17491
- function boundaryKey(previousBlockId, nextBlockId) {
17492
- return makeBoundaryId(previousBlockId, nextBlockId);
17493
- }
17494
17933
 
17495
17934
  //#endregion
17496
17935
  //#region ../theme-core/src/site-styles/contentTemplateDesignIntent.ts
@@ -20855,7 +21294,7 @@ function underlineNavLinkStyle(colorToken, underlineColorToken, typography = "pl
20855
21294
  }] }
20856
21295
  }, typography);
20857
21296
  }
20858
- function pillNavLinkStyle(colorToken, backgroundToken = "primary", opacity = 10, typography = "plain") {
21297
+ function pillNavLinkStyle(colorToken, backgroundToken$1 = "primary", opacity = 10, typography = "plain") {
20859
21298
  return applyNavTypography({
20860
21299
  name: "Pill",
20861
21300
  typography: "body",
@@ -20869,14 +21308,14 @@ function pillNavLinkStyle(colorToken, backgroundToken = "primary", opacity = 10,
20869
21308
  hover: [{
20870
21309
  effectId: "background-on-hover-alpha",
20871
21310
  options: {
20872
- colorToken: backgroundToken,
21311
+ colorToken: backgroundToken$1,
20873
21312
  opacity
20874
21313
  }
20875
21314
  }],
20876
21315
  active: [{
20877
21316
  effectId: "background-on-hover-alpha",
20878
21317
  options: {
20879
- colorToken: backgroundToken,
21318
+ colorToken: backgroundToken$1,
20880
21319
  opacity: opacity + 5
20881
21320
  }
20882
21321
  }]
@@ -21930,151 +22369,650 @@ function classifyRuntimeStyleFields(input) {
21930
22369
  fields: []
21931
22370
  };
21932
22371
  }
21933
- if (!input.authority) {
21934
- return {
21935
- kind: "unsupported-manual-review",
21936
- fields
21937
- };
22372
+ if (!input.authority) {
22373
+ return {
22374
+ kind: "unsupported-manual-review",
22375
+ fields
22376
+ };
22377
+ }
22378
+ switch (input.authority.kind) {
22379
+ case "theme-v2": return {
22380
+ kind: "interpretable-override",
22381
+ fields,
22382
+ authority: input.authority
22383
+ };
22384
+ case "advanced-detached": return {
22385
+ kind: "advanced-detached",
22386
+ fields,
22387
+ authority: input.authority
22388
+ };
22389
+ case "legacy-raw": return {
22390
+ kind: "unsupported-manual-review",
22391
+ fields,
22392
+ authority: input.authority
22393
+ };
22394
+ default: return assertNever$1(input.authority);
22395
+ }
22396
+ }
22397
+ function canWriteThemeV2RuntimeFields(authority) {
22398
+ return authority?.kind === "theme-v2";
22399
+ }
22400
+ function blockReasonForAuthority(authority) {
22401
+ if (!authority) {
22402
+ return "missing-theme-v2-authority";
22403
+ }
22404
+ switch (authority?.kind) {
22405
+ case "advanced-detached": return "advanced-detached";
22406
+ case "legacy-raw": return "legacy-raw";
22407
+ default: return assertNever$1(authority);
22408
+ }
22409
+ }
22410
+ /**
22411
+ * Replace every compiler-owned field on a block's content with a fresh compiled
22412
+ * set — the one primitive both writers of compiled output share.
22413
+ *
22414
+ * Replacement rather than merge is the whole point: a field the new
22415
+ * compilation does not produce must *go*, or a recompile onto a Look that asks
22416
+ * for no card leaves the old `_cardStyles` sitting there claiming a surface
22417
+ * nobody asked for. Every writer that discovers compiler ownership for itself
22418
+ * eventually forgets one of these names, so nobody does.
22419
+ *
22420
+ * Pure, and deliberately free of authority checks — those belong to the
22421
+ * persistence wrapper, not to the shape of the content.
22422
+ */
22423
+ function withCompiledRuntimeStyleFields(input) {
22424
+ const authored = Object.fromEntries(Object.entries(stripStaleTransitionStyleGroup(input.content)).filter(([fieldName]) => !themeV2CompilerOwnedFieldNames.includes(fieldName)));
22425
+ return {
22426
+ ...authored,
22427
+ ...input.fields
22428
+ };
22429
+ }
22430
+ /**
22431
+ * Persisted Theme V2 output is compile-at-write: once a block has Theme V2
22432
+ * authority, compiler-owned runtime fields are replaced in the same content
22433
+ * version. Preview-only compile-at-read callers should use compileBlockRuntimeFields
22434
+ * and avoid persisting these fields.
22435
+ */
22436
+ function replaceCompiledRuntimeStyleFields(input) {
22437
+ if (!canWriteThemeV2RuntimeFields(input.authority)) {
22438
+ return {
22439
+ kind: "blocked",
22440
+ reason: blockReasonForAuthority(input.authority),
22441
+ content: input.content
22442
+ };
22443
+ }
22444
+ return {
22445
+ kind: "applied",
22446
+ content: withCompiledRuntimeStyleFields(input)
22447
+ };
22448
+ }
22449
+ function uniqueBlockIds(values) {
22450
+ return [...new Set(values)];
22451
+ }
22452
+ function collectPageStyleAuthorityBuckets(styleAuthorities) {
22453
+ const themeV2BlockIds = [];
22454
+ const legacyRawBlockIds = [];
22455
+ const advancedDetachedBlockIds = [];
22456
+ for (const entry of styleAuthorities) {
22457
+ switch (entry.authority.kind) {
22458
+ case "theme-v2":
22459
+ themeV2BlockIds.push(entry.blockId);
22460
+ break;
22461
+ case "legacy-raw":
22462
+ legacyRawBlockIds.push(entry.blockId);
22463
+ break;
22464
+ case "advanced-detached":
22465
+ advancedDetachedBlockIds.push(entry.blockId);
22466
+ break;
22467
+ default: assertNever$1(entry.authority);
22468
+ }
22469
+ }
22470
+ return {
22471
+ themeV2BlockIds: uniqueBlockIds(themeV2BlockIds),
22472
+ legacyRawBlockIds: uniqueBlockIds(legacyRawBlockIds),
22473
+ advancedDetachedBlockIds: uniqueBlockIds(advancedDetachedBlockIds)
22474
+ };
22475
+ }
22476
+ function summarizePageStyleAuthorities(state) {
22477
+ const { themeV2BlockIds, legacyRawBlockIds, advancedDetachedBlockIds } = collectPageStyleAuthorityBuckets(state.styleAuthorities);
22478
+ if (themeV2BlockIds.length === 0 && legacyRawBlockIds.length === 0 && advancedDetachedBlockIds.length === 0) {
22479
+ return {
22480
+ kind: "no-runtime-authority",
22481
+ themeV2BlockIds: [],
22482
+ legacyRawBlockIds: [],
22483
+ advancedDetachedBlockIds: []
22484
+ };
22485
+ }
22486
+ if (legacyRawBlockIds.length > 0) {
22487
+ return {
22488
+ kind: "manual-review-required",
22489
+ themeV2BlockIds,
22490
+ legacyRawBlockIds,
22491
+ advancedDetachedBlockIds
22492
+ };
22493
+ }
22494
+ if (themeV2BlockIds.length === 0) {
22495
+ return {
22496
+ kind: "advanced-detached-only",
22497
+ themeV2BlockIds: [],
22498
+ legacyRawBlockIds: [],
22499
+ advancedDetachedBlockIds
22500
+ };
22501
+ }
22502
+ if (advancedDetachedBlockIds.length > 0) {
22503
+ return {
22504
+ kind: "mixed-authority",
22505
+ themeV2BlockIds,
22506
+ legacyRawBlockIds: [],
22507
+ advancedDetachedBlockIds
22508
+ };
22509
+ }
22510
+ return {
22511
+ kind: "theme-v2-only",
22512
+ themeV2BlockIds,
22513
+ legacyRawBlockIds: [],
22514
+ advancedDetachedBlockIds: []
22515
+ };
22516
+ }
22517
+
22518
+ //#endregion
22519
+ //#region ../theme-core/src/site-styles/pageFlowPresets.ts
22520
+ /**
22521
+ * Must remain non-empty; downstream code indexes `[0]` as the documented default.
22522
+ */
22523
+ const pageFlowPresets = [
22524
+ {
22525
+ id: asPageFlowPresetId("flow:quiet"),
22526
+ label: "Quiet",
22527
+ boundaryStrategy: "quiet",
22528
+ defaultBoundaryOptionId: noneBoundaryOption.id
22529
+ },
22530
+ {
22531
+ id: asPageFlowPresetId("flow:balanced"),
22532
+ label: "Balanced",
22533
+ boundaryStrategy: "balanced",
22534
+ defaultBoundaryOptionId: softBoundaryOption.id
22535
+ },
22536
+ {
22537
+ id: asPageFlowPresetId("flow:expressive"),
22538
+ label: "Expressive",
22539
+ boundaryStrategy: "expressive",
22540
+ defaultBoundaryOptionId: spaciousBoundaryOption.id
22541
+ }
22542
+ ];
22543
+ /**
22544
+ * The break a join gets when nothing more specific applies. An unknown preset
22545
+ * id (a stale or policy-owned one) falls back to the first boundary option,
22546
+ * and `preset` is null so callers can tell the two apart.
22547
+ */
22548
+ function pageFlowDefaultBoundary(pageFlowPresetId) {
22549
+ const preset = pageFlowPresets.find((candidate$1) => candidate$1.id === pageFlowPresetId) ?? null;
22550
+ return {
22551
+ preset,
22552
+ boundaryOptionId: preset?.defaultBoundaryOptionId ?? boundaryOptions[0].id
22553
+ };
22554
+ }
22555
+
22556
+ //#endregion
22557
+ //#region ../theme-core/src/site-styles/lookResolution.ts
22558
+ function blockAppearancePresetSupportsBlockKind(preset, blockKind) {
22559
+ return preset.blockKinds.some((candidate$1) => candidate$1 === blockKind);
22560
+ }
22561
+ function preferredAppearanceTargetForBlockKind(blockKind) {
22562
+ switch (blockKind) {
22563
+ case "block.event-calendar":
22564
+ case "block.event-listing": return "events";
22565
+ default: return null;
22566
+ }
22567
+ }
22568
+ function sortCompatibleBlockAppearancePresets(blockKind, options) {
22569
+ const preferredTarget = preferredAppearanceTargetForBlockKind(blockKind);
22570
+ if (!preferredTarget) {
22571
+ return options;
22572
+ }
22573
+ return [...options].sort((left, right) => {
22574
+ const leftPreset = appearancePresets.find((preset) => preset.id === left.id);
22575
+ const rightPreset = appearancePresets.find((preset) => preset.id === right.id);
22576
+ const leftPreferred = leftPreset?.target === preferredTarget;
22577
+ const rightPreferred = rightPreset?.target === preferredTarget;
22578
+ if (leftPreferred === rightPreferred) {
22579
+ return 0;
22580
+ }
22581
+ return leftPreferred ? -1 : 1;
22582
+ });
22583
+ }
22584
+ function blockAppearancePresetSupportsLayoutProfile(preset, profile, input = {}) {
22585
+ const constraints = preset.constraints;
22586
+ if (!constraints || !constraints.layoutFamilies && !constraints.layoutVariantKeys) {
22587
+ return true;
22588
+ }
22589
+ if (!profile) {
22590
+ return input.allowLayoutConstrainedPresetWithoutProfile ?? true;
22591
+ }
22592
+ return layoutProfileSupportsConstraints(profile, constraints);
22593
+ }
22594
+ function resolveBlockAppearancePreset(preset, layoutProfile = null) {
22595
+ return {
22596
+ id: asAppearancePresetId(preset.id),
22597
+ label: preset.label,
22598
+ design: resolveBlockAppearancePresetDesign(preset, layoutProfile)
22599
+ };
22600
+ }
22601
+ function getCompatibleBlockAppearancePresets(input) {
22602
+ const options = appearancePresets.flatMap((preset) => {
22603
+ if (!blockAppearancePresetSupportsBlockKind(preset, input.blockKind) || !blockAppearancePresetSupportsLayoutProfile(preset, input.layoutProfile ?? null, { allowLayoutConstrainedPresetWithoutProfile: input.allowLayoutConstrainedPresetWithoutProfile })) {
22604
+ return [];
22605
+ }
22606
+ return [resolveBlockAppearancePreset(preset, input.layoutProfile ?? null)];
22607
+ });
22608
+ const sortedOptions = sortCompatibleBlockAppearancePresets(input.blockKind, options);
22609
+ const recommendedIds = input.siteStyle?.recommendedAppearancePresetIds ?? [];
22610
+ if (recommendedIds.length === 0) {
22611
+ return sortedOptions;
22612
+ }
22613
+ const recommended = recommendedIds.flatMap((presetId) => sortedOptions.find((option) => option.id === presetId) ?? []);
22614
+ const alreadyIncludedIds = new Set(recommended.map((option) => option.id));
22615
+ return [...recommended, ...sortedOptions.filter((option) => !alreadyIncludedIds.has(option.id))];
22616
+ }
22617
+
22618
+ //#endregion
22619
+ //#region ../theme-core/src/site-styles/siteStyleBlockPolicies.ts
22620
+ const defaultPolicyPageFlowPresetId = asPageFlowPresetId("generated-default");
22621
+ const southWestTwerkSchoolSiteStyleId = asSiteStyleId("site-style:south-west-twerk-school");
22622
+ const heroBrandScrimFixedFrameLookId = asAppearancePresetId("hero-brand-scrim-fixed-frame");
22623
+ const classicHeroLayoutVariantKey = asLayoutVariantKey("classic");
22624
+ const microHeroLayoutVariantKey = asLayoutVariantKey("micro");
22625
+ const waveBoundaryOptionId = asBoundaryOptionId("break:wave");
22626
+ const registeredSiteStyleBlockPolicies = [{
22627
+ siteStyleId: southWestTwerkSchoolSiteStyleId,
22628
+ kind: "locked-look-and-layouts",
22629
+ blockKind: "block.hero",
22630
+ lookId: heroBrandScrimFixedFrameLookId,
22631
+ allowedLayoutVariantKeys: [classicHeroLayoutVariantKey, microHeroLayoutVariantKey],
22632
+ defaultLayoutVariantKey: classicHeroLayoutVariantKey,
22633
+ defaultBoundaryAfter: {
22634
+ optionId: waveBoundaryOptionId,
22635
+ mode: "default-only"
22636
+ },
22637
+ customize: {
22638
+ hideDisallowedLooks: true,
22639
+ hideDisallowedLayouts: true
22640
+ }
22641
+ }];
22642
+ function siteStyleBlockPolicies() {
22643
+ return registeredSiteStyleBlockPolicies;
22644
+ }
22645
+ function buildInitialPolicyPageDesignIntent(siteStyleId) {
22646
+ return {
22647
+ schemaVersion: pageDesignIntentSchemaVersion,
22648
+ siteStyleId,
22649
+ pageFlowPresetId: defaultPolicyPageFlowPresetId,
22650
+ blockLookPins: [],
22651
+ boundaryPins: []
22652
+ };
22653
+ }
22654
+ function deriveSiteStyleBlockPolicy(input) {
22655
+ return registeredSiteStyleBlockPolicies.find((policy) => policy.siteStyleId === input.siteStyleId && policy.blockKind === input.blockKind) ?? { kind: "open" };
22656
+ }
22657
+ function isLayoutVariantAllowedByPolicy(policy, layoutVariantKey) {
22658
+ switch (policy.kind) {
22659
+ case "open": return true;
22660
+ case "locked-look-and-layouts": return policy.allowedLayoutVariantKeys.includes(layoutVariantKey);
22661
+ default: return assertNever$1(policy, "Unhandled site style block policy");
21938
22662
  }
21939
- switch (input.authority.kind) {
21940
- case "theme-v2": return {
21941
- kind: "interpretable-override",
21942
- fields,
21943
- authority: input.authority
21944
- };
21945
- case "advanced-detached": return {
21946
- kind: "advanced-detached",
21947
- fields,
21948
- authority: input.authority
21949
- };
21950
- case "legacy-raw": return {
21951
- kind: "unsupported-manual-review",
21952
- fields,
21953
- authority: input.authority
21954
- };
21955
- default: return assertNever$1(input.authority);
22663
+ }
22664
+ function normalizeLayoutVariantForPolicy(policy, layoutVariantKey) {
22665
+ switch (policy.kind) {
22666
+ case "open": return layoutVariantKey ?? null;
22667
+ case "locked-look-and-layouts": return layoutVariantKey && isLayoutVariantAllowedByPolicy(policy, layoutVariantKey) ? layoutVariantKey : policy.defaultLayoutVariantKey;
22668
+ default: return assertNever$1(policy, "Unhandled site style block policy");
21956
22669
  }
21957
22670
  }
21958
- function canWriteThemeV2RuntimeFields(authority) {
21959
- return authority?.kind === "theme-v2";
22671
+ function filterLayoutProfilesForPolicy(policy, layoutProfiles) {
22672
+ switch (policy.kind) {
22673
+ case "open": return layoutProfiles;
22674
+ case "locked-look-and-layouts": return layoutProfiles.filter((profile) => isLayoutVariantAllowedByPolicy(policy, profile.variantKey));
22675
+ default: return assertNever$1(policy, "Unhandled site style block policy");
22676
+ }
21960
22677
  }
21961
- function blockReasonForAuthority(authority) {
21962
- if (!authority) {
21963
- return "missing-theme-v2-authority";
22678
+ function isLookAllowedByPolicy(policy, lookId) {
22679
+ switch (policy.kind) {
22680
+ case "open": return true;
22681
+ case "locked-look-and-layouts": return lookId === policy.lookId;
22682
+ default: return assertNever$1(policy, "Unhandled site style block policy");
21964
22683
  }
21965
- switch (authority?.kind) {
21966
- case "advanced-detached": return "advanced-detached";
21967
- case "legacy-raw": return "legacy-raw";
21968
- default: return assertNever$1(authority);
22684
+ }
22685
+ function filterLookIdsForPolicy(policy, looks) {
22686
+ switch (policy.kind) {
22687
+ case "open": return looks;
22688
+ case "locked-look-and-layouts": return looks.filter((look) => look.id === policy.lookId);
22689
+ default: return assertNever$1(policy, "Unhandled site style block policy");
21969
22690
  }
21970
22691
  }
21971
- /**
21972
- * Replace every compiler-owned field on a block's content with a fresh compiled
21973
- * set — the one primitive both writers of compiled output share.
21974
- *
21975
- * Replacement rather than merge is the whole point: a field the new
21976
- * compilation does not produce must *go*, or a recompile onto a Look that asks
21977
- * for no card leaves the old `_cardStyles` sitting there claiming a surface
21978
- * nobody asked for. Every writer that discovers compiler ownership for itself
21979
- * eventually forgets one of these names, so nobody does.
21980
- *
21981
- * Pure, and deliberately free of authority checks — those belong to the
21982
- * persistence wrapper, not to the shape of the content.
21983
- */
21984
- function withCompiledRuntimeStyleFields(input) {
21985
- const authored = Object.fromEntries(Object.entries(input.content).filter(([fieldName]) => !themeV2CompilerOwnedFieldNames.includes(fieldName)));
22692
+ function requiredLookChoice(lookId) {
21986
22693
  return {
21987
- ...authored,
21988
- ...input.fields
22694
+ kind: "user-pinned",
22695
+ value: lookId
21989
22696
  };
21990
22697
  }
21991
22698
  /**
21992
- * Persisted Theme V2 output is compile-at-write: once a block has Theme V2
21993
- * authority, compiler-owned runtime fields are replaced in the same content
21994
- * version. Preview-only compile-at-read callers should use compileBlockRuntimeFields
21995
- * and avoid persisting these fields.
22699
+ * Writes the look pins a locked-look site style requires.
22700
+ *
22701
+ * Boundary pins are never written: a site style's default break after a block
22702
+ * is resolved at read time by `resolveJoinTreatment`, so it shows as a default
22703
+ * and follows later changes to the style (issue 1422). Pins already stored,
22704
+ * including ones an earlier version wrote on the style's behalf, are left as
22705
+ * they are.
21996
22706
  */
21997
- function replaceCompiledRuntimeStyleFields(input) {
21998
- if (!canWriteThemeV2RuntimeFields(input.authority)) {
21999
- return {
22000
- kind: "blocked",
22001
- reason: blockReasonForAuthority(input.authority),
22002
- content: input.content
22003
- };
22707
+ function applySiteStyleBlockPoliciesToPageDesignIntent(intent, blocks) {
22708
+ const policyLookPins = blocks.flatMap((block) => {
22709
+ const policy = deriveSiteStyleBlockPolicy({
22710
+ siteStyleId: intent.siteStyleId,
22711
+ blockKind: block.blockKind
22712
+ });
22713
+ switch (policy.kind) {
22714
+ case "open": return [];
22715
+ case "locked-look-and-layouts": return [{
22716
+ blockId: block.blockId,
22717
+ blockKind: block.blockKind,
22718
+ choice: requiredLookChoice(policy.lookId)
22719
+ }];
22720
+ default: return assertNever$1(policy, "Unhandled site style block policy");
22721
+ }
22722
+ });
22723
+ if (policyLookPins.length === 0) {
22724
+ return intent;
22004
22725
  }
22726
+ const policyPinBlockIds = new Set(policyLookPins.map((pin) => pin.blockId));
22005
22727
  return {
22006
- kind: "applied",
22007
- content: withCompiledRuntimeStyleFields(input)
22728
+ ...intent,
22729
+ blockLookPins: [...intent.blockLookPins.filter((pin) => !policyPinBlockIds.has(pin.blockId)), ...policyLookPins]
22008
22730
  };
22009
22731
  }
22010
- function uniqueBlockIds(values) {
22011
- return [...new Set(values)];
22732
+ function planBlockContentVariantPolicyUpdate(input) {
22733
+ switch (input.policy.kind) {
22734
+ case "open": return { kind: "unchanged" };
22735
+ case "locked-look-and-layouts": {
22736
+ const currentVariant = typeof input.content.variant === "string" ? asLayoutVariantKey(input.content.variant) : null;
22737
+ return currentVariant && isLayoutVariantAllowedByPolicy(input.policy, currentVariant) ? { kind: "unchanged" } : {
22738
+ kind: "set-variant",
22739
+ variantKey: input.policy.defaultLayoutVariantKey
22740
+ };
22741
+ }
22742
+ default: return assertNever$1(input.policy, "Unhandled site style block policy");
22743
+ }
22012
22744
  }
22013
- function collectPageStyleAuthorityBuckets(styleAuthorities) {
22014
- const themeV2BlockIds = [];
22015
- const legacyRawBlockIds = [];
22016
- const advancedDetachedBlockIds = [];
22017
- for (const entry of styleAuthorities) {
22018
- switch (entry.authority.kind) {
22019
- case "theme-v2":
22020
- themeV2BlockIds.push(entry.blockId);
22021
- break;
22022
- case "legacy-raw":
22023
- legacyRawBlockIds.push(entry.blockId);
22024
- break;
22025
- case "advanced-detached":
22026
- advancedDetachedBlockIds.push(entry.blockId);
22027
- break;
22028
- default: assertNever$1(entry.authority);
22745
+ function applyBlockContentVariantPolicy(input) {
22746
+ const plan = planBlockContentVariantPolicyUpdate(input);
22747
+ switch (plan.kind) {
22748
+ case "unchanged": return input.content;
22749
+ case "set-variant": return {
22750
+ ...input.content,
22751
+ variant: plan.variantKey
22752
+ };
22753
+ default: return assertNever$1(plan, "Unhandled block content variant policy plan");
22754
+ }
22755
+ }
22756
+ function auditPageDesignIntentPolicy(input) {
22757
+ return input.blocks.flatMap((block) => {
22758
+ const policy = deriveSiteStyleBlockPolicy({
22759
+ siteStyleId: input.intent.siteStyleId,
22760
+ blockKind: block.blockKind
22761
+ });
22762
+ switch (policy.kind) {
22763
+ case "open": return [];
22764
+ case "locked-look-and-layouts": {
22765
+ const choice = resolvePageDesignBlockLookChoice(input.intent, block.blockId);
22766
+ const lookIssue = choice.kind === "auto" ? {
22767
+ kind: "missing-required-look-pin",
22768
+ blockId: block.blockId,
22769
+ blockKind: block.blockKind,
22770
+ requiredLookId: policy.lookId
22771
+ } : choice.value === policy.lookId ? null : {
22772
+ kind: "unsupported-look-pin",
22773
+ blockId: block.blockId,
22774
+ blockKind: block.blockKind,
22775
+ actualLookId: choice.value,
22776
+ requiredLookId: policy.lookId
22777
+ };
22778
+ const layoutIssue = block.currentLayoutVariantKey && !isLayoutVariantAllowedByPolicy(policy, block.currentLayoutVariantKey) ? {
22779
+ kind: "unsupported-layout-variant",
22780
+ blockId: block.blockId,
22781
+ blockKind: block.blockKind,
22782
+ actualLayoutVariantKey: block.currentLayoutVariantKey,
22783
+ defaultLayoutVariantKey: policy.defaultLayoutVariantKey
22784
+ } : null;
22785
+ const issues = [];
22786
+ if (lookIssue) {
22787
+ issues.push(lookIssue);
22788
+ }
22789
+ if (layoutIssue) {
22790
+ issues.push(layoutIssue);
22791
+ }
22792
+ return issues;
22793
+ }
22794
+ default: return assertNever$1(policy, "Unhandled site style block policy");
22795
+ }
22796
+ });
22797
+ }
22798
+ function auditPageDesignAuthorityPolicy(input) {
22799
+ if (input.intent) {
22800
+ return auditPageDesignIntentPolicy({
22801
+ intent: input.intent,
22802
+ blocks: input.blocks
22803
+ });
22804
+ }
22805
+ const policyBlockCount = input.blocks.filter((block) => deriveSiteStyleBlockPolicy({
22806
+ siteStyleId: input.siteStyleId,
22807
+ blockKind: block.blockKind
22808
+ }).kind !== "open").length;
22809
+ return policyBlockCount === 0 ? [] : [{
22810
+ kind: "missing-page-design-authority",
22811
+ siteStyleId: input.siteStyleId,
22812
+ policyBlockCount
22813
+ }];
22814
+ }
22815
+ function assertRegisteredSiteStyleBlockPoliciesAreValid() {
22816
+ registeredSiteStyleBlockPolicies.forEach((policy) => {
22817
+ if (!policy.allowedLayoutVariantKeys.includes(policy.defaultLayoutVariantKey)) {
22818
+ throw new Error(`Site style block policy for ${policy.siteStyleId} ${policy.blockKind} has a default layout outside its allowed layouts.`);
22819
+ }
22820
+ const preset = appearancePresets.find((candidate$1) => candidate$1.id === policy.lookId && blockAppearancePresetSupportsBlockKind(candidate$1, policy.blockKind));
22821
+ if (!preset) {
22822
+ throw new Error(`Site style block policy for ${policy.siteStyleId} ${policy.blockKind} references an unavailable Look ${policy.lookId}.`);
22029
22823
  }
22824
+ });
22825
+ }
22826
+ function autoChoiceAllowedByPolicy(policy) {
22827
+ switch (policy.kind) {
22828
+ case "open": return autoDesignChoice();
22829
+ case "locked-look-and-layouts": return requiredLookChoice(policy.lookId);
22830
+ default: return assertNever$1(policy, "Unhandled site style block policy");
22030
22831
  }
22832
+ }
22833
+
22834
+ //#endregion
22835
+ //#region ../theme-core/src/site-styles/joinTreatment.ts
22836
+ /** Narrows any join (a resolved intent included) to its public, intent-only form. */
22837
+ function toPageRuntimeJoin(join) {
22031
22838
  return {
22032
- themeV2BlockIds: uniqueBlockIds(themeV2BlockIds),
22033
- legacyRawBlockIds: uniqueBlockIds(legacyRawBlockIds),
22034
- advancedDetachedBlockIds: uniqueBlockIds(advancedDetachedBlockIds)
22839
+ previousBlockId: join.previousBlockId,
22840
+ nextBlockId: join.nextBlockId,
22841
+ treatment: join.treatment
22035
22842
  };
22036
22843
  }
22037
- function summarizePageStyleAuthorities(state) {
22038
- const { themeV2BlockIds, legacyRawBlockIds, advancedDetachedBlockIds } = collectPageStyleAuthorityBuckets(state.styleAuthorities);
22039
- if (themeV2BlockIds.length === 0 && legacyRawBlockIds.length === 0 && advancedDetachedBlockIds.length === 0) {
22844
+ /**
22845
+ * The single rule set for which treatment a join gets. Candidates arrive in
22846
+ * priority order; the first supported one wins.
22847
+ *
22848
+ * - `none` ends the search and yields no join.
22849
+ * - An unsupported candidate (a legacy option id, or a structured treatment the
22850
+ * catalogue lacks) falls through to the next one. When it was the user's
22851
+ * pin, the pin is reported unsupported and the existing
22852
+ * `pinned-boundary-option-unavailable` issue is raised.
22853
+ */
22854
+ function resolveJoinTreatment(input) {
22855
+ const boundaryId = makeBoundaryId(input.previousBlockId, input.nextBlockId);
22856
+ const selection = selectCandidate(input.candidates);
22857
+ const selectedOutcome = selection.kind === "selected" ? selection.outcome : { kind: "none" };
22858
+ const pin = pinStatus(input.candidates, selectedOutcome);
22859
+ const issues = pin.kind === "unsupported" ? [{
22860
+ kind: "pinned-boundary-option-unavailable",
22861
+ boundaryId,
22862
+ presetId: pin.optionId,
22863
+ feedback: "user-facing"
22864
+ }] : [];
22865
+ if (selection.kind === "exhausted") {
22040
22866
  return {
22041
- kind: "no-runtime-authority",
22042
- themeV2BlockIds: [],
22043
- legacyRawBlockIds: [],
22044
- advancedDetachedBlockIds: []
22867
+ join: null,
22868
+ selectedSource: null,
22869
+ pin,
22870
+ issues
22045
22871
  };
22046
22872
  }
22047
- if (legacyRawBlockIds.length > 0) {
22873
+ if (selection.outcome.kind === "none") {
22048
22874
  return {
22049
- kind: "manual-review-required",
22050
- themeV2BlockIds,
22051
- legacyRawBlockIds,
22052
- advancedDetachedBlockIds
22875
+ join: null,
22876
+ selectedSource: selection.candidate.source,
22877
+ pin,
22878
+ issues
22053
22879
  };
22054
22880
  }
22055
- if (themeV2BlockIds.length === 0) {
22056
- return {
22057
- kind: "advanced-detached-only",
22058
- themeV2BlockIds: [],
22059
- legacyRawBlockIds: [],
22060
- advancedDetachedBlockIds
22061
- };
22881
+ return {
22882
+ selectedSource: selection.candidate.source,
22883
+ join: {
22884
+ boundaryId,
22885
+ previousBlockId: input.previousBlockId,
22886
+ nextBlockId: input.nextBlockId,
22887
+ treatment: selection.outcome.treatment,
22888
+ source: selection.candidate.source
22889
+ },
22890
+ pin,
22891
+ issues
22892
+ };
22893
+ }
22894
+ function pinStatus(candidates, selectedOutcome) {
22895
+ const pinned = candidates.find((candidate$1) => candidate$1.source.kind === "user-pinned");
22896
+ if (!pinned) return { kind: "none" };
22897
+ const outcome = candidateOutcome(pinned.treatment);
22898
+ return outcome.kind === "unsupported" ? {
22899
+ kind: "unsupported",
22900
+ optionId: outcome.boundaryOptionId ?? fallbackOptionId(selectedOutcome)
22901
+ } : { kind: "supported" };
22902
+ }
22903
+ function selectCandidate(candidates) {
22904
+ for (const candidate$1 of candidates) {
22905
+ const outcome = candidateOutcome(candidate$1.treatment);
22906
+ switch (outcome.kind) {
22907
+ case "decorative":
22908
+ case "none": return {
22909
+ kind: "selected",
22910
+ candidate: candidate$1,
22911
+ outcome
22912
+ };
22913
+ case "unsupported": continue;
22914
+ default: return assertNever$1(outcome, "Unhandled join candidate outcome");
22915
+ }
22062
22916
  }
22063
- if (advancedDetachedBlockIds.length > 0) {
22064
- return {
22065
- kind: "mixed-authority",
22066
- themeV2BlockIds,
22067
- legacyRawBlockIds: [],
22068
- advancedDetachedBlockIds
22069
- };
22917
+ return { kind: "exhausted" };
22918
+ }
22919
+ function candidateOutcome(treatment) {
22920
+ if (treatment.kind === "none") return { kind: "none" };
22921
+ const intent = transitionIntentForTreatment(treatment);
22922
+ return intent ? {
22923
+ kind: "decorative",
22924
+ treatment: intent
22925
+ } : {
22926
+ kind: "unsupported",
22927
+ boundaryOptionId: boundaryOptionIdForTreatment(treatment)
22928
+ };
22929
+ }
22930
+ /**
22931
+ * Matches the editor's existing issue payload: the pin's own option id when
22932
+ * it has one, otherwise the option the join fell back to.
22933
+ */
22934
+ function fallbackOptionId(outcome) {
22935
+ switch (outcome.kind) {
22936
+ case "decorative": return boundaryOptionForTransitionIntent(outcome.treatment).id;
22937
+ case "none": return noneBoundaryOption.id;
22938
+ default: return assertNever$1(outcome, "Unhandled join fallback outcome");
22070
22939
  }
22940
+ }
22941
+ /**
22942
+ * The page-design defaults for a join, in priority order: the site style's
22943
+ * default break after the previous block, then the page-flow default, which
22944
+ * is always last.
22945
+ */
22946
+ function pageDesignDefaultJoinCandidates(input) {
22947
+ const policy = deriveSiteStyleBlockPolicy({
22948
+ siteStyleId: input.siteStyleId,
22949
+ blockKind: input.previousBlockKind
22950
+ });
22951
+ const siteStyleDefault = policy.kind === "locked-look-and-layouts" && policy.defaultBoundaryAfter ? [{
22952
+ source: {
22953
+ kind: "site-style-default",
22954
+ siteStyleId: input.siteStyleId
22955
+ },
22956
+ treatment: boundaryTreatmentForBoundaryOptionId(policy.defaultBoundaryAfter.optionId)
22957
+ }] : [];
22958
+ return [...siteStyleDefault, pageFlowDefaultJoinCandidate(input.pageFlowPresetId)];
22959
+ }
22960
+ /** The last-resort page-design default, also the source of a fully unsupported join. */
22961
+ function pageFlowDefaultJoinCandidate(pageFlowPresetId) {
22962
+ const pageFlow = pageFlowDefaultBoundary(pageFlowPresetId);
22071
22963
  return {
22072
- kind: "theme-v2-only",
22073
- themeV2BlockIds,
22074
- legacyRawBlockIds: [],
22075
- advancedDetachedBlockIds: []
22964
+ source: {
22965
+ kind: "page-flow-default",
22966
+ pageFlowPresetId: pageFlow.preset?.id ?? null
22967
+ },
22968
+ treatment: boundaryTreatmentForBoundaryOptionId(pageFlow.boundaryOptionId)
22076
22969
  };
22077
22970
  }
22971
+ /**
22972
+ * Page-design source adapter. Priority: the user's pin, then the defaults.
22973
+ * The pin is offered even when unsupported so the rule function can report it.
22974
+ */
22975
+ function joinCandidatesFromPageDesign(intent, siteStyleId, previous, next) {
22976
+ const choice = resolvePageDesignBoundaryChoice(intent, previous.blockId, next.blockId);
22977
+ return [...choice.kind === "user-pinned" ? [{
22978
+ source: { kind: "user-pinned" },
22979
+ treatment: choice.value
22980
+ }] : [], ...pageDesignDefaultJoinCandidates({
22981
+ siteStyleId,
22982
+ pageFlowPresetId: intent.pageFlowPresetId,
22983
+ previousBlockKind: previous.blockKind
22984
+ })];
22985
+ }
22986
+ /**
22987
+ * Generated-design source adapter. Mirrors today's materialisation: the
22988
+ * boundary intent's explicit choice, then the block design's explicit choice,
22989
+ * then the automatic default of no break.
22990
+ */
22991
+ function joinCandidatesFromGeneratedIntent(block) {
22992
+ return [
22993
+ ...explicitGeneratedCandidate(block.boundaryIntent.transitionAfter, "boundary-intent"),
22994
+ ...explicitGeneratedCandidate(block.design.transitionAfter, "block-design"),
22995
+ {
22996
+ source: {
22997
+ kind: "generated",
22998
+ origin: "automatic"
22999
+ },
23000
+ treatment: { kind: "none" }
23001
+ }
23002
+ ];
23003
+ }
23004
+ function explicitGeneratedCandidate(choice, origin) {
23005
+ return choice.kind === "explicit" ? [{
23006
+ source: {
23007
+ kind: "generated",
23008
+ origin
23009
+ },
23010
+ treatment: treatmentForTransitionIntent(choice.value)
23011
+ }] : [];
23012
+ }
23013
+ function treatmentForTransitionIntent(transition) {
23014
+ return transition === "none" ? { kind: "none" } : transitionCatalogueEntryForIntent(transition).treatment;
23015
+ }
22078
23016
 
22079
23017
  //#endregion
22080
23018
  //#region ../theme-core/src/site-styles/imageSurfaceBackground.ts
@@ -22188,13 +23126,15 @@ const compiledRuntimeStyleFieldNames = [
22188
23126
  "_contentCardStyles",
22189
23127
  "_heroLayoutStyles",
22190
23128
  "_cardStyles",
22191
- "_transitionStyles",
22192
23129
  "blockLook",
22193
- "blockComposition",
22194
- "styleGroup"
23130
+ "blockComposition"
22195
23131
  ];
23132
+ /**
23133
+ * Removes every compiler-owned field, plus a stale transition `styleGroup`.
23134
+ * An authored `styleGroup` is content and survives.
23135
+ */
22196
23136
  function stripCompiledRuntimeStyleFields(content) {
22197
- return Object.fromEntries(Object.entries(content).filter(([fieldName]) => !compiledRuntimeStyleFieldNames.includes(fieldName)));
23137
+ return Object.fromEntries(Object.entries(stripStaleTransitionStyleGroup(content)).filter(([fieldName]) => !compiledRuntimeStyleFieldNames.includes(fieldName)));
22198
23138
  }
22199
23139
  function surfaceResolutionContextFromPalette(palette) {
22200
23140
  return {
@@ -22213,7 +23153,7 @@ function defineComponentRuntimeRules(rules) {
22213
23153
  }
22214
23154
  /**
22215
23155
  * Default design rules translate semantic page-level choices into the current
22216
- * runtime's section background and transition style-group vocabulary.
23156
+ * runtime's section background vocabulary.
22217
23157
  */
22218
23158
  const defaultDesignRuntimeRules = defineDesignRuntimeRules({
22219
23159
  sectionSurfaces: {
@@ -22248,8 +23188,7 @@ const defaultDesignRuntimeRules = defineDesignRuntimeRules({
22248
23188
  color: "surface",
22249
23189
  opacity: .88
22250
23190
  }
22251
- },
22252
- transitionClassName: (transition, fromSurface, toSurface) => transition === "none" ? null : `site-style-transition-${transition}-from-${fromSurface}-to-${toSurface}`
23191
+ }
22253
23192
  });
22254
23193
  /**
22255
23194
  * Default component rules translate frame and item-surface semantics into the
@@ -22475,26 +23414,6 @@ function compileCardStyles(input) {
22475
23414
  if (!role) return undefined;
22476
23415
  return cardStylesOverrideSchema.parse({ role });
22477
23416
  }
22478
- function sectionAdjacencyForBlock(input) {
22479
- if (input.sectionAdjacency) {
22480
- return input.sectionAdjacency;
22481
- }
22482
- if (input.nextSectionSurface) {
22483
- return {
22484
- kind: "next-section",
22485
- surface: input.nextSectionSurface
22486
- };
22487
- }
22488
- return { kind: "page-end" };
22489
- }
22490
- function transitionTargetSurfaceForAdjacency(adjacency) {
22491
- switch (adjacency.kind) {
22492
- case "next-section":
22493
- case "terminal-surface": return adjacency.surface;
22494
- case "page-end": return null;
22495
- default: return assertNever$1(adjacency);
22496
- }
22497
- }
22498
23417
  function resolveEyebrowTreatmentForRuntime(block, rules) {
22499
23418
  return block.eyebrowTreatment ?? rules.defaultEyebrowTreatment ?? block.appliedEyebrowTreatment;
22500
23419
  }
@@ -22512,19 +23431,6 @@ function compileBlockRuntimeFields(input) {
22512
23431
  });
22513
23432
  const shouldSuppressContainerFrame = shouldMoveFrameToHeroContentCard(input.block);
22514
23433
  const cardStyles = compileCardStyles({ cardRole: input.block.appliedCardRole });
22515
- const transitionTargetSurface = transitionTargetSurfaceForAdjacency(sectionAdjacencyForBlock(input));
22516
- const transitionClassName = transitionTargetSurface ? designRules.transitionClassName(input.block.appliedTransitionAfter, visualSectionSurface, transitionTargetSurface) : null;
22517
- const transitionRuntime = transitionTargetSurface && transitionClassName ? compileTransitionRuntime({
22518
- treatment: input.block.appliedTransitionAfter,
22519
- from: {
22520
- kind: "surface",
22521
- surface: visualSectionSurface
22522
- },
22523
- to: {
22524
- kind: "surface",
22525
- surface: transitionTargetSurface
22526
- }
22527
- }) : null;
22528
23434
  return {
22529
23435
  blockId: input.block.blockId,
22530
23436
  visualSectionSurface,
@@ -22549,72 +23455,10 @@ function compileBlockRuntimeFields(input) {
22549
23455
  ...cardStyles ? { _cardStyles: cardStyles } : {},
22550
23456
  ...heroRuntimeFields,
22551
23457
  ...input.block.appearancePresetId ? { blockLook: input.block.appearancePresetId } : {},
22552
- ...input.block.appliedComposition ? { blockComposition: input.block.appliedComposition } : {},
22553
- ...transitionRuntime ? { _transitionStyles: transitionRuntime } : {},
22554
- ...transitionClassName ? { styleGroup: transitionClassName } : {}
22555
- }
22556
- };
22557
- }
22558
- function compileTransitionRuntime(input) {
22559
- return {
22560
- treatment: input.treatment,
22561
- from: input.from,
22562
- to: input.to,
22563
- cssVars: {
22564
- from: input.from.kind === "surface" && input.from.surface === "hero-image" ? "transparent" : transitionEndpointCssValue(input.from),
22565
- to: transitionEndpointCssValue(input.to)
22566
- }
22567
- };
22568
- }
22569
- function compileBoundaryRuntime(input) {
22570
- const rules = input.rules ?? defaultDesignRuntimeRules;
22571
- const styleGroup = rules.transitionClassName(input.treatment, input.fromSurface, input.toSurface);
22572
- if (!styleGroup) return null;
22573
- const metrics = boundaryRuntimeMetricsForTransition(input.treatment);
22574
- if (!metrics) return null;
22575
- const transition = compileTransitionRuntime({
22576
- treatment: input.treatment,
22577
- from: {
22578
- kind: "surface",
22579
- surface: input.fromSurface
22580
- },
22581
- to: {
22582
- kind: "surface",
22583
- surface: input.toSurface
23458
+ ...input.block.appliedComposition ? { blockComposition: input.block.appliedComposition } : {}
22584
23459
  }
22585
- });
22586
- return {
22587
- boundaryId: input.boundaryId,
22588
- previousBlockId: input.previousBlockId,
22589
- nextBlockId: input.nextBlockId,
22590
- treatment: transition.treatment,
22591
- metrics,
22592
- from: transition.from,
22593
- to: transition.to,
22594
- cssVars: transition.cssVars,
22595
- styleGroup
22596
23460
  };
22597
23461
  }
22598
- function transitionEndpointCssValue(endpoint) {
22599
- switch (endpoint.kind) {
22600
- case "color-token": return themeColorTokenCssValue(endpoint.token);
22601
- case "surface": return themeColorTokenCssValue(tokenForTransitionSurface(endpoint.surface));
22602
- default: return assertNever$1(endpoint);
22603
- }
22604
- }
22605
- function tokenForTransitionSurface(surface$1) {
22606
- switch (surface$1) {
22607
- case "base":
22608
- case "hero-image": return "background";
22609
- case "muted-band": return "muted";
22610
- case "accent-band": return "accent";
22611
- case "brand-depth-band": return "primary-800";
22612
- default: return assertNever$1(surface$1);
22613
- }
22614
- }
22615
- function themeColorTokenCssValue(token) {
22616
- return `rgb(var(--tb-${token}))`;
22617
- }
22618
23462
  /**
22619
23463
  * Compiles block runtime fields and writes them into content only when Theme V2
22620
23464
  * owns that block's runtime-style fields.
@@ -22627,58 +23471,30 @@ function compileBlockRuntimeFieldsIntoContent(input) {
22627
23471
  authority: input.authority
22628
23472
  });
22629
23473
  }
22630
- /** Compiles a resolved page plan into legacy runtime fields for each resolved block. */
23474
+ /**
23475
+ * Compiles a resolved page plan into legacy runtime fields for each resolved
23476
+ * block, plus the public runtime joins between them.
23477
+ *
23478
+ * Joins come from `resolveJoinTreatment`; plans no longer carry a treatment.
23479
+ * Only joins whose two blocks were compiled are kept, and they leave as intent
23480
+ * only: the blocks renderer composes each one from final content after
23481
+ * omission, including whether it draws at all (issue 1422).
23482
+ */
22631
23483
  function compilePageRuntimeFields(input) {
22632
- const visualSurfaces = input.resolved.blocks.map(resolveVisualSectionSurfaceForBlock);
22633
- const terminalSectionAdjacency = input.terminalSectionAdjacency ?? { kind: "page-end" };
22634
- const designRules = input.designRules ?? defaultDesignRuntimeRules;
22635
- const blocks = input.resolved.blocks.map((block, index) => {
22636
- const nextSurface = visualSurfaces[index + 1];
22637
- const compiled = compileBlockRuntimeFields({
22638
- block,
22639
- surfaceResolutionContext: input.surfaceResolutionContext,
22640
- designRules: input.designRules,
22641
- componentRules: input.componentRules,
22642
- sectionSpacing: input.sectionSpacing,
22643
- sectionAdjacency: nextSurface ? {
22644
- kind: "next-section",
22645
- surface: nextSurface
22646
- } : terminalSectionAdjacency
22647
- });
22648
- return {
22649
- ...compiled,
22650
- fields: stripPageLevelTransitionFields(compiled.fields)
22651
- };
22652
- });
22653
- const boundaries = input.resolved.blocks.flatMap((block, index) => {
22654
- const nextBlock = input.resolved.blocks[index + 1];
22655
- const fromSurface = visualSurfaces[index];
22656
- const toSurface = visualSurfaces[index + 1];
22657
- if (!nextBlock || !fromSurface || !toSurface) {
22658
- return [];
22659
- }
22660
- const boundary = compileBoundaryRuntime({
22661
- boundaryId: `boundary:${block.blockId}:${nextBlock.blockId}`,
22662
- previousBlockId: block.blockId,
22663
- nextBlockId: nextBlock.blockId,
22664
- treatment: block.appliedTransitionAfter,
22665
- fromSurface,
22666
- toSurface,
22667
- rules: designRules
22668
- });
22669
- return boundary ? [boundary] : [];
22670
- });
23484
+ const blocks = input.resolved.blocks.map((block) => compileBlockRuntimeFields({
23485
+ block,
23486
+ surfaceResolutionContext: input.surfaceResolutionContext,
23487
+ designRules: input.designRules,
23488
+ componentRules: input.componentRules,
23489
+ sectionSpacing: input.sectionSpacing
23490
+ }));
23491
+ const compiledIds = new Set(blocks.map((block) => block.blockId));
23492
+ const joins = input.joins.filter((join) => compiledIds.has(join.previousBlockId) && compiledIds.has(join.nextBlockId)).map(toPageRuntimeJoin);
22671
23493
  return {
22672
23494
  blocks,
22673
- boundaries
23495
+ joins
22674
23496
  };
22675
23497
  }
22676
- function stripPageLevelTransitionFields(fields) {
22677
- const { _transitionStyles, styleGroup, ...rest } = fields;
22678
- void _transitionStyles;
22679
- void styleGroup;
22680
- return rest;
22681
- }
22682
23498
  const paletteByRecipe = {
22683
23499
  "brand-led": [
22684
23500
  {
@@ -24738,371 +25554,173 @@ const curatedSiteStylesById = new Map(curatedSiteStyles.map((style) => [style.id
24738
25554
  * only to the compiler.
24739
25555
  */
24740
25556
  function headerStyleRecipeForTheme(theme, styleId) {
24741
- const siteStyle = theme?.siteStyleId ? getCuratedSiteStyle(theme.siteStyleId) : null;
24742
- return resolveHeaderStyleRecipe(siteStyle, styleId);
24743
- }
24744
- function getCuratedSiteStyle(id) {
24745
- return curatedSiteStylesById.get(id) ?? null;
24746
- }
24747
- function designRuntimeRulesForSiteStyle(siteStyle) {
24748
- return siteStyle?.designRuntimeRules ?? defaultDesignRuntimeRules;
24749
- }
24750
- function designRuntimeRulesForTheme(theme) {
24751
- const siteStyleId = theme?.siteStyleId;
24752
- if (!siteStyleId) return defaultDesignRuntimeRules;
24753
- return designRuntimeRulesForSiteStyle(curatedSiteStylesById.get(asSiteStyleId(siteStyleId)) ?? null);
24754
- }
24755
- function componentRuntimeRulesForSiteStyle(siteStyle) {
24756
- return siteStyle?.componentRuntimeRules ?? defaultComponentRuntimeRules;
24757
- }
24758
- function componentRuntimeRulesForTheme(theme) {
24759
- const siteStyleId = theme?.siteStyleId;
24760
- if (!siteStyleId) return defaultComponentRuntimeRules;
24761
- return componentRuntimeRulesForSiteStyle(curatedSiteStylesById.get(asSiteStyleId(siteStyleId)) ?? null);
24762
- }
24763
- function defaultBlockVariantForSiteStyle(siteStyle, blockKind) {
24764
- return siteStyle.defaultBlockVariants?.[blockKind] ?? null;
24765
- }
24766
-
24767
- //#endregion
24768
- //#region ../theme-core/src/site-styles/pageDesignEditorCapabilities.ts
24769
- function cardRoleCapabilityOf(capability) {
24770
- return capability?.kind === "designed" ? capability.cardRoles : "unsupported";
24771
- }
24772
- function backgroundSupportForCapability(capability) {
24773
- if (capability === null) {
24774
- return {
24775
- kind: "unsupported",
24776
- reason: "missing-design-capability"
24777
- };
24778
- }
24779
- if (capability.kind === "not-designed") {
24780
- return {
24781
- kind: "unsupported",
24782
- reason: capability.reason
24783
- };
24784
- }
24785
- return capability.sectionSurfaces === "supported" ? {
24786
- kind: "supported",
24787
- richBackgrounds: capability.richBackgrounds ?? "unsupported"
24788
- } : {
24789
- kind: "unsupported",
24790
- reason: "section-surfaces-unsupported"
24791
- };
24792
- }
24793
- /**
24794
- * The capability-derived half of a block's editor read model, in one call so
24795
- * the read-model builder does not grow a projection per axis.
24796
- */
24797
- function capabilitySupportFor(capability) {
24798
- return {
24799
- backgroundSupport: backgroundSupportForCapability(capability),
24800
- cardRoles: cardRoleCapabilityOf(capability)
24801
- };
24802
- }
24803
-
24804
- //#endregion
24805
- //#region ../theme-core/src/site-styles/lookResolution.ts
24806
- function blockAppearancePresetSupportsBlockKind(preset, blockKind) {
24807
- return preset.blockKinds.some((candidate$1) => candidate$1 === blockKind);
24808
- }
24809
- function preferredAppearanceTargetForBlockKind(blockKind) {
24810
- switch (blockKind) {
24811
- case "block.event-calendar":
24812
- case "block.event-listing": return "events";
24813
- default: return null;
24814
- }
24815
- }
24816
- function sortCompatibleBlockAppearancePresets(blockKind, options) {
24817
- const preferredTarget = preferredAppearanceTargetForBlockKind(blockKind);
24818
- if (!preferredTarget) {
24819
- return options;
24820
- }
24821
- return [...options].sort((left, right) => {
24822
- const leftPreset = appearancePresets.find((preset) => preset.id === left.id);
24823
- const rightPreset = appearancePresets.find((preset) => preset.id === right.id);
24824
- const leftPreferred = leftPreset?.target === preferredTarget;
24825
- const rightPreferred = rightPreset?.target === preferredTarget;
24826
- if (leftPreferred === rightPreferred) {
24827
- return 0;
24828
- }
24829
- return leftPreferred ? -1 : 1;
24830
- });
24831
- }
24832
- function blockAppearancePresetSupportsLayoutProfile(preset, profile, input = {}) {
24833
- const constraints = preset.constraints;
24834
- if (!constraints || !constraints.layoutFamilies && !constraints.layoutVariantKeys) {
24835
- return true;
24836
- }
24837
- if (!profile) {
24838
- return input.allowLayoutConstrainedPresetWithoutProfile ?? true;
24839
- }
24840
- return layoutProfileSupportsConstraints(profile, constraints);
24841
- }
24842
- function resolveBlockAppearancePreset(preset, layoutProfile = null) {
24843
- return {
24844
- id: asAppearancePresetId(preset.id),
24845
- label: preset.label,
24846
- design: resolveBlockAppearancePresetDesign(preset, layoutProfile)
24847
- };
24848
- }
24849
- function getCompatibleBlockAppearancePresets(input) {
24850
- const options = appearancePresets.flatMap((preset) => {
24851
- if (!blockAppearancePresetSupportsBlockKind(preset, input.blockKind) || !blockAppearancePresetSupportsLayoutProfile(preset, input.layoutProfile ?? null, { allowLayoutConstrainedPresetWithoutProfile: input.allowLayoutConstrainedPresetWithoutProfile })) {
24852
- return [];
24853
- }
24854
- return [resolveBlockAppearancePreset(preset, input.layoutProfile ?? null)];
24855
- });
24856
- const sortedOptions = sortCompatibleBlockAppearancePresets(input.blockKind, options);
24857
- const recommendedIds = input.siteStyle?.recommendedAppearancePresetIds ?? [];
24858
- if (recommendedIds.length === 0) {
24859
- return sortedOptions;
24860
- }
24861
- const recommended = recommendedIds.flatMap((presetId) => sortedOptions.find((option) => option.id === presetId) ?? []);
24862
- const alreadyIncludedIds = new Set(recommended.map((option) => option.id));
24863
- return [...recommended, ...sortedOptions.filter((option) => !alreadyIncludedIds.has(option.id))];
25557
+ const siteStyle = theme?.siteStyleId ? getCuratedSiteStyle(theme.siteStyleId) : null;
25558
+ return resolveHeaderStyleRecipe(siteStyle, styleId);
24864
25559
  }
24865
-
24866
- //#endregion
24867
- //#region ../theme-core/src/site-styles/siteStyleBlockPolicies.ts
24868
- const defaultPolicyPageFlowPresetId = asPageFlowPresetId("generated-default");
24869
- const southWestTwerkSchoolSiteStyleId = asSiteStyleId("site-style:south-west-twerk-school");
24870
- const heroBrandScrimFixedFrameLookId = asAppearancePresetId("hero-brand-scrim-fixed-frame");
24871
- const classicHeroLayoutVariantKey = asLayoutVariantKey("classic");
24872
- const microHeroLayoutVariantKey = asLayoutVariantKey("micro");
24873
- const waveBoundaryOptionId = asBoundaryOptionId("break:wave");
24874
- const registeredSiteStyleBlockPolicies = [{
24875
- siteStyleId: southWestTwerkSchoolSiteStyleId,
24876
- kind: "locked-look-and-layouts",
24877
- blockKind: "block.hero",
24878
- lookId: heroBrandScrimFixedFrameLookId,
24879
- allowedLayoutVariantKeys: [classicHeroLayoutVariantKey, microHeroLayoutVariantKey],
24880
- defaultLayoutVariantKey: classicHeroLayoutVariantKey,
24881
- defaultBoundaryAfter: {
24882
- optionId: waveBoundaryOptionId,
24883
- mode: "default-only"
24884
- },
24885
- customize: {
24886
- hideDisallowedLooks: true,
24887
- hideDisallowedLayouts: true
24888
- }
24889
- }];
24890
- function siteStyleBlockPolicies() {
24891
- return registeredSiteStyleBlockPolicies;
25560
+ function getCuratedSiteStyle(id) {
25561
+ return curatedSiteStylesById.get(id) ?? null;
24892
25562
  }
24893
- function buildInitialPolicyPageDesignIntent(siteStyleId) {
24894
- return {
24895
- schemaVersion: pageDesignIntentSchemaVersion,
24896
- siteStyleId,
24897
- pageFlowPresetId: defaultPolicyPageFlowPresetId,
24898
- blockLookPins: [],
24899
- boundaryPins: []
24900
- };
25563
+ function designRuntimeRulesForSiteStyle(siteStyle) {
25564
+ return siteStyle?.designRuntimeRules ?? defaultDesignRuntimeRules;
24901
25565
  }
24902
- function hasPolicyForBlocks(input) {
24903
- return input.blocks.some((block) => deriveSiteStyleBlockPolicy({
24904
- siteStyleId: input.siteStyleId,
24905
- blockKind: block.blockKind
24906
- }).kind !== "open");
25566
+ function designRuntimeRulesForTheme(theme) {
25567
+ const siteStyleId = theme?.siteStyleId;
25568
+ if (!siteStyleId) return defaultDesignRuntimeRules;
25569
+ return designRuntimeRulesForSiteStyle(curatedSiteStylesById.get(asSiteStyleId(siteStyleId)) ?? null);
24907
25570
  }
24908
- function deriveSiteStyleBlockPolicy(input) {
24909
- return registeredSiteStyleBlockPolicies.find((policy) => policy.siteStyleId === input.siteStyleId && policy.blockKind === input.blockKind) ?? { kind: "open" };
25571
+ function componentRuntimeRulesForSiteStyle(siteStyle) {
25572
+ return siteStyle?.componentRuntimeRules ?? defaultComponentRuntimeRules;
24910
25573
  }
24911
- function isLayoutVariantAllowedByPolicy(policy, layoutVariantKey) {
24912
- switch (policy.kind) {
24913
- case "open": return true;
24914
- case "locked-look-and-layouts": return policy.allowedLayoutVariantKeys.includes(layoutVariantKey);
24915
- default: return assertNever$1(policy, "Unhandled site style block policy");
24916
- }
25574
+ function componentRuntimeRulesForTheme(theme) {
25575
+ const siteStyleId = theme?.siteStyleId;
25576
+ if (!siteStyleId) return defaultComponentRuntimeRules;
25577
+ return componentRuntimeRulesForSiteStyle(curatedSiteStylesById.get(asSiteStyleId(siteStyleId)) ?? null);
24917
25578
  }
24918
- function normalizeLayoutVariantForPolicy(policy, layoutVariantKey) {
24919
- switch (policy.kind) {
24920
- case "open": return layoutVariantKey ?? null;
24921
- case "locked-look-and-layouts": return layoutVariantKey && isLayoutVariantAllowedByPolicy(policy, layoutVariantKey) ? layoutVariantKey : policy.defaultLayoutVariantKey;
24922
- default: return assertNever$1(policy, "Unhandled site style block policy");
24923
- }
25579
+ function defaultBlockVariantForSiteStyle(siteStyle, blockKind) {
25580
+ return siteStyle.defaultBlockVariants?.[blockKind] ?? null;
24924
25581
  }
24925
- function filterLayoutProfilesForPolicy(policy, layoutProfiles) {
24926
- switch (policy.kind) {
24927
- case "open": return layoutProfiles;
24928
- case "locked-look-and-layouts": return layoutProfiles.filter((profile) => isLayoutVariantAllowedByPolicy(policy, profile.variantKey));
24929
- default: return assertNever$1(policy, "Unhandled site style block policy");
24930
- }
25582
+
25583
+ //#endregion
25584
+ //#region ../theme-core/src/site-styles/pageDesignEditorCapabilities.ts
25585
+ function cardRoleCapabilityOf(capability) {
25586
+ return capability?.kind === "designed" ? capability.cardRoles : "unsupported";
24931
25587
  }
24932
- function isLookAllowedByPolicy(policy, lookId) {
24933
- switch (policy.kind) {
24934
- case "open": return true;
24935
- case "locked-look-and-layouts": return lookId === policy.lookId;
24936
- default: return assertNever$1(policy, "Unhandled site style block policy");
25588
+ function backgroundSupportForCapability(capability) {
25589
+ if (capability === null) {
25590
+ return {
25591
+ kind: "unsupported",
25592
+ reason: "missing-design-capability"
25593
+ };
24937
25594
  }
24938
- }
24939
- function filterLookIdsForPolicy(policy, looks) {
24940
- switch (policy.kind) {
24941
- case "open": return looks;
24942
- case "locked-look-and-layouts": return looks.filter((look) => look.id === policy.lookId);
24943
- default: return assertNever$1(policy, "Unhandled site style block policy");
25595
+ if (capability.kind === "not-designed") {
25596
+ return {
25597
+ kind: "unsupported",
25598
+ reason: capability.reason
25599
+ };
24944
25600
  }
24945
- }
24946
- function requiredLookChoice(lookId) {
24947
- return {
24948
- kind: "user-pinned",
24949
- value: lookId
25601
+ return capability.sectionSurfaces === "supported" ? {
25602
+ kind: "supported",
25603
+ richBackgrounds: capability.richBackgrounds ?? "unsupported"
25604
+ } : {
25605
+ kind: "unsupported",
25606
+ reason: "section-surfaces-unsupported"
24950
25607
  };
24951
25608
  }
24952
- function requiredBoundaryChoice(optionId) {
25609
+ /**
25610
+ * The capability-derived half of a block's editor read model, in one call so
25611
+ * the read-model builder does not grow a projection per axis.
25612
+ */
25613
+ function capabilitySupportFor(capability) {
24953
25614
  return {
24954
- kind: "user-pinned",
24955
- value: boundaryTreatmentForBoundaryOptionId(optionId)
25615
+ backgroundSupport: backgroundSupportForCapability(capability),
25616
+ cardRoles: cardRoleCapabilityOf(capability)
24956
25617
  };
24957
25618
  }
24958
- function applySiteStyleBlockPoliciesToPageDesignIntent(intent, blocks) {
24959
- const policyLookPins = blocks.flatMap((block) => {
24960
- const policy = deriveSiteStyleBlockPolicy({
24961
- siteStyleId: intent.siteStyleId,
24962
- blockKind: block.blockKind
24963
- });
24964
- switch (policy.kind) {
24965
- case "open": return [];
24966
- case "locked-look-and-layouts": return [{
24967
- blockId: block.blockId,
24968
- blockKind: block.blockKind,
24969
- choice: requiredLookChoice(policy.lookId)
24970
- }];
24971
- default: return assertNever$1(policy, "Unhandled site style block policy");
24972
- }
25619
+
25620
+ //#endregion
25621
+ //#region ../theme-core/src/site-styles/pageDesignEditorBoundaries.ts
25622
+ function buildBoundaryReadModels(input) {
25623
+ return input.blocks.flatMap((previousBlock, index) => {
25624
+ const nextBlock = input.blocks[index + 1];
25625
+ return nextBlock ? [buildBoundaryReadModel({
25626
+ siteStyleId: input.siteStyleId,
25627
+ pageFlowPresetId: input.intent.pageFlowPresetId,
25628
+ pin: resolvePageDesignBoundaryChoice(input.intent, previousBlock.blockId, nextBlock.blockId),
25629
+ previousBlock,
25630
+ nextBlock
25631
+ })] : [];
24973
25632
  });
24974
- const policyBoundaryPins = blocks.flatMap((block, index) => {
24975
- const nextBlock = blocks[index + 1];
24976
- if (!nextBlock) {
24977
- return [];
24978
- }
24979
- const policy = deriveSiteStyleBlockPolicy({
24980
- siteStyleId: intent.siteStyleId,
24981
- blockKind: block.blockKind
24982
- });
24983
- switch (policy.kind) {
24984
- case "open": return [];
24985
- case "locked-look-and-layouts": {
24986
- const defaultBoundary = policy.defaultBoundaryAfter;
24987
- if (!defaultBoundary) {
24988
- return [];
24989
- }
24990
- const currentChoice = resolvePageDesignBoundaryChoice(intent, block.blockId, nextBlock.blockId);
24991
- return currentChoice.kind === "auto" ? [buildBoundaryPin(block.blockId, nextBlock.blockId, requiredBoundaryChoice(defaultBoundary.optionId))] : [];
24992
- }
24993
- default: return assertNever$1(policy, "Unhandled site style block policy");
24994
- }
25633
+ }
25634
+ function buildBoundaryReadModel(input) {
25635
+ const previousBlockId = input.previousBlock.blockId;
25636
+ const nextBlockId = input.nextBlock.blockId;
25637
+ const defaults = pageDesignDefaultJoinCandidates({
25638
+ siteStyleId: input.siteStyleId,
25639
+ pageFlowPresetId: input.pageFlowPresetId,
25640
+ previousBlockKind: input.previousBlock.blockKind
24995
25641
  });
24996
- if (policyLookPins.length === 0 && policyBoundaryPins.length === 0) {
24997
- return intent;
24998
- }
24999
- const policyPinBlockIds = new Set(policyLookPins.map((pin) => pin.blockId));
25642
+ const projection = input.pin.kind === "user-pinned" ? pinnedBreakChoice(resolveJoinTreatment({
25643
+ candidates: [{
25644
+ source: { kind: "user-pinned" },
25645
+ treatment: input.pin.value
25646
+ }, ...defaults],
25647
+ previousBlockId,
25648
+ nextBlockId
25649
+ })) : autoBreakChoice(resolveJoinTreatment({
25650
+ candidates: defaults,
25651
+ previousBlockId,
25652
+ nextBlockId
25653
+ }), input.pageFlowPresetId);
25000
25654
  return {
25001
- ...intent,
25002
- blockLookPins: [...intent.blockLookPins.filter((pin) => !policyPinBlockIds.has(pin.blockId)), ...policyLookPins],
25003
- boundaryPins: [...intent.boundaryPins, ...policyBoundaryPins]
25655
+ boundary: {
25656
+ boundaryId: makeBoundaryId(previousBlockId, nextBlockId),
25657
+ previousBlockId,
25658
+ nextBlockId,
25659
+ breakChoice: projection.breakChoice,
25660
+ availableBreaks: boundaryOptions
25661
+ },
25662
+ issues: projection.issues
25004
25663
  };
25005
25664
  }
25006
- function planBlockContentVariantPolicyUpdate(input) {
25007
- switch (input.policy.kind) {
25008
- case "open": return { kind: "unchanged" };
25009
- case "locked-look-and-layouts": {
25010
- const currentVariant = typeof input.content.variant === "string" ? asLayoutVariantKey(input.content.variant) : null;
25011
- return currentVariant && isLayoutVariantAllowedByPolicy(input.policy, currentVariant) ? { kind: "unchanged" } : {
25012
- kind: "set-variant",
25013
- variantKey: input.policy.defaultLayoutVariantKey
25014
- };
25015
- }
25016
- default: return assertNever$1(input.policy, "Unhandled site style block policy");
25017
- }
25018
- }
25019
- function applyBlockContentVariantPolicy(input) {
25020
- const plan = planBlockContentVariantPolicyUpdate(input);
25021
- switch (plan.kind) {
25022
- case "unchanged": return input.content;
25023
- case "set-variant": return {
25024
- ...input.content,
25025
- variant: plan.variantKey
25026
- };
25027
- default: return assertNever$1(plan, "Unhandled block content variant policy plan");
25028
- }
25029
- }
25030
- function auditPageDesignIntentPolicy(input) {
25031
- return input.blocks.flatMap((block) => {
25032
- const policy = deriveSiteStyleBlockPolicy({
25033
- siteStyleId: input.intent.siteStyleId,
25034
- blockKind: block.blockKind
25035
- });
25036
- switch (policy.kind) {
25037
- case "open": return [];
25038
- case "locked-look-and-layouts": {
25039
- const choice = resolvePageDesignBlockLookChoice(input.intent, block.blockId);
25040
- const lookIssue = choice.kind === "auto" ? {
25041
- kind: "missing-required-look-pin",
25042
- blockId: block.blockId,
25043
- blockKind: block.blockKind,
25044
- requiredLookId: policy.lookId
25045
- } : choice.value === policy.lookId ? null : {
25046
- kind: "unsupported-look-pin",
25047
- blockId: block.blockId,
25048
- blockKind: block.blockKind,
25049
- actualLookId: choice.value,
25050
- requiredLookId: policy.lookId
25051
- };
25052
- const layoutIssue = block.currentLayoutVariantKey && !isLayoutVariantAllowedByPolicy(policy, block.currentLayoutVariantKey) ? {
25053
- kind: "unsupported-layout-variant",
25054
- blockId: block.blockId,
25055
- blockKind: block.blockKind,
25056
- actualLayoutVariantKey: block.currentLayoutVariantKey,
25057
- defaultLayoutVariantKey: policy.defaultLayoutVariantKey
25058
- } : null;
25059
- const issues = [];
25060
- if (lookIssue) {
25061
- issues.push(lookIssue);
25062
- }
25063
- if (layoutIssue) {
25064
- issues.push(layoutIssue);
25065
- }
25066
- return issues;
25067
- }
25068
- default: return assertNever$1(policy, "Unhandled site style block policy");
25069
- }
25070
- });
25071
- }
25072
- function auditPageDesignAuthorityPolicy(input) {
25073
- if (input.intent) {
25074
- return auditPageDesignIntentPolicy({
25075
- intent: input.intent,
25076
- blocks: input.blocks
25077
- });
25078
- }
25079
- const policyBlockCount = input.blocks.filter((block) => deriveSiteStyleBlockPolicy({
25080
- siteStyleId: input.siteStyleId,
25081
- blockKind: block.blockKind
25082
- }).kind !== "open").length;
25083
- return policyBlockCount === 0 ? [] : [{
25084
- kind: "missing-page-design-authority",
25085
- siteStyleId: input.siteStyleId,
25086
- policyBlockCount
25087
- }];
25088
- }
25089
- function assertRegisteredSiteStyleBlockPoliciesAreValid() {
25090
- registeredSiteStyleBlockPolicies.forEach((policy) => {
25091
- if (!policy.allowedLayoutVariantKeys.includes(policy.defaultLayoutVariantKey)) {
25092
- throw new Error(`Site style block policy for ${policy.siteStyleId} ${policy.blockKind} has a default layout outside its allowed layouts.`);
25093
- }
25094
- const preset = appearancePresets.find((candidate$1) => candidate$1.id === policy.lookId && blockAppearancePresetSupportsBlockKind(candidate$1, policy.blockKind));
25095
- if (!preset) {
25096
- throw new Error(`Site style block policy for ${policy.siteStyleId} ${policy.blockKind} references an unavailable Look ${policy.lookId}.`);
25097
- }
25098
- });
25665
+ function autoBreakChoice(resolution, pageFlowPresetId) {
25666
+ return {
25667
+ breakChoice: {
25668
+ kind: "auto",
25669
+ source: resolution.selectedSource ?? pageFlowDefaultJoinCandidate(pageFlowPresetId).source,
25670
+ effectiveValue: effectiveBreak(resolution)
25671
+ },
25672
+ issues: resolution.issues
25673
+ };
25099
25674
  }
25100
- function autoChoiceAllowedByPolicy(policy) {
25101
- switch (policy.kind) {
25102
- case "open": return autoDesignChoice();
25103
- case "locked-look-and-layouts": return requiredLookChoice(policy.lookId);
25104
- default: return assertNever$1(policy, "Unhandled site style block policy");
25675
+ function pinnedBreakChoice(resolution) {
25676
+ const effectiveValue = effectiveBreak(resolution);
25677
+ switch (resolution.pin.kind) {
25678
+ case "unsupported": return {
25679
+ breakChoice: {
25680
+ kind: "user-pinned",
25681
+ value: resolution.pin.optionId,
25682
+ status: "unsupported",
25683
+ effectiveValue
25684
+ },
25685
+ issues: resolution.issues
25686
+ };
25687
+ case "supported":
25688
+ case "none": return {
25689
+ breakChoice: {
25690
+ kind: "user-pinned",
25691
+ value: effectiveValue ?? noneBoundaryOption.id,
25692
+ status: "supported",
25693
+ effectiveValue
25694
+ },
25695
+ issues: resolution.issues
25696
+ };
25697
+ default: return assertNever$1(resolution.pin, "Unhandled join pin status");
25698
+ }
25699
+ }
25700
+ /** The effective break as the option the editor shows; null when nothing was usable. */
25701
+ function effectiveBreak(resolution) {
25702
+ if (resolution.join) {
25703
+ return boundaryOptionForTransitionIntent(resolution.join.treatment).id;
25105
25704
  }
25705
+ return resolution.selectedSource ? noneBoundaryOption.id : null;
25706
+ }
25707
+ /**
25708
+ * Re-resolves one boundary of an editor model for a changed break choice,
25709
+ * through the same rules the model was built with. The preview uses it for
25710
+ * optimistic updates, so a reset lands on whichever default the rules pick
25711
+ * (site style or Page Flow). Null when either block is not in the model.
25712
+ */
25713
+ function resolvePageDesignEditorBoundaryForChoice(input) {
25714
+ const previousBlock = input.editorModel.blocks.find((block) => block.blockId === input.previousBlockId);
25715
+ const nextBlock = input.editorModel.blocks.find((block) => block.blockId === input.nextBlockId);
25716
+ if (!previousBlock || !nextBlock) return null;
25717
+ return buildBoundaryReadModel({
25718
+ siteStyleId: input.editorModel.siteStyleId,
25719
+ pageFlowPresetId: input.editorModel.pageFlow.value,
25720
+ pin: input.choice,
25721
+ previousBlock,
25722
+ nextBlock
25723
+ }).boundary;
25106
25724
  }
25107
25725
 
25108
25726
  //#endregion
@@ -25129,29 +25747,6 @@ const backgroundImagePromotedFieldPath = "_sectionStyles.background.image";
25129
25747
  * Look/Layout semantics support them.
25130
25748
  */
25131
25749
  const normalEditorCuratedPromotedFieldPaths = [backgroundImagePromotedFieldPath];
25132
- /**
25133
- * Must remain non-empty; downstream code indexes `[0]` as the documented default.
25134
- */
25135
- const pageFlowPresets = [
25136
- {
25137
- id: asPageFlowPresetId("flow:quiet"),
25138
- label: "Quiet",
25139
- boundaryStrategy: "quiet",
25140
- defaultBoundaryOptionId: noneBoundaryOption.id
25141
- },
25142
- {
25143
- id: asPageFlowPresetId("flow:balanced"),
25144
- label: "Balanced",
25145
- boundaryStrategy: "balanced",
25146
- defaultBoundaryOptionId: softBoundaryOption.id
25147
- },
25148
- {
25149
- id: asPageFlowPresetId("flow:expressive"),
25150
- label: "Expressive",
25151
- boundaryStrategy: "expressive",
25152
- defaultBoundaryOptionId: spaciousBoundaryOption.id
25153
- }
25154
- ];
25155
25750
  function buildPageDesignEditorReadModel(input) {
25156
25751
  const defaultPageFlow = pageFlowPresets[0];
25157
25752
  const currentPageFlow = pageFlowPresets.find((preset) => preset.id === input.intent.pageFlowPresetId) ?? null;
@@ -25239,8 +25834,8 @@ function buildPageDesignEditorReadModel(input) {
25239
25834
  };
25240
25835
  });
25241
25836
  const boundaryResults = buildBoundaryReadModels({
25242
- intent: input.intent,
25243
- blocks: input.blocks
25837
+ ...input,
25838
+ blocks: blockResults.map((result) => result.block)
25244
25839
  });
25245
25840
  return {
25246
25841
  siteStyleId: input.siteStyleId,
@@ -25484,62 +26079,6 @@ function editorChoiceForLook(input) {
25484
26079
  })
25485
26080
  });
25486
26081
  }
25487
- function buildBoundaryReadModels(input) {
25488
- return input.blocks.flatMap((previous, index) => {
25489
- const next = input.blocks[index + 1];
25490
- if (!next) {
25491
- return [];
25492
- }
25493
- const boundaryId = makeBoundaryId(previous.blockId, next.blockId);
25494
- const choice = resolvePageDesignBoundaryChoice(input.intent, previous.blockId, next.blockId);
25495
- const breakResult = boundaryEditorChoiceFromResolvedChoice({
25496
- choice,
25497
- defaultValue: autoBoundaryOptionForIntent(input.intent),
25498
- unsupportedIssue: (presetId) => ({
25499
- kind: "pinned-boundary-option-unavailable",
25500
- boundaryId,
25501
- presetId,
25502
- feedback: "user-facing"
25503
- })
25504
- });
25505
- return {
25506
- boundary: {
25507
- boundaryId,
25508
- previousBlockId: previous.blockId,
25509
- nextBlockId: next.blockId,
25510
- breakChoice: breakResult.choice,
25511
- availableBreaks: boundaryOptions
25512
- },
25513
- issues: compactIssues([breakResult.issue])
25514
- };
25515
- });
25516
- }
25517
- function boundaryEditorChoiceFromResolvedChoice(input) {
25518
- switch (input.choice.kind) {
25519
- case "auto": return {
25520
- choice: {
25521
- kind: "auto",
25522
- effectiveValue: input.defaultValue
25523
- },
25524
- issue: null
25525
- };
25526
- case "user-pinned": {
25527
- const option = boundaryOptionForTreatment(input.choice.value);
25528
- const fallbackValue = boundaryOptionIdForTreatment(input.choice.value);
25529
- const issueValue = fallbackValue ?? input.defaultValue ?? noneBoundaryOption.id;
25530
- return {
25531
- choice: {
25532
- kind: "user-pinned",
25533
- value: issueValue,
25534
- status: option ? "supported" : "unsupported",
25535
- effectiveValue: option?.id ?? input.defaultValue
25536
- },
25537
- issue: option ? null : input.unsupportedIssue(issueValue)
25538
- };
25539
- }
25540
- default: return assertNever$1(input.choice, "Unhandled boundary design choice");
25541
- }
25542
- }
25543
26082
  function editorChoiceFromResolvedChoice(input) {
25544
26083
  switch (input.choice.kind) {
25545
26084
  case "auto": return {
@@ -25564,10 +26103,6 @@ function editorChoiceFromResolvedChoice(input) {
25564
26103
  default: return assertNever$1(input.choice, "Unhandled page design choice");
25565
26104
  }
25566
26105
  }
25567
- function autoBoundaryOptionForIntent(intent) {
25568
- const pageFlow = pageFlowPresets.find((preset) => preset.id === intent.pageFlowPresetId);
25569
- return pageFlow?.defaultBoundaryOptionId ?? boundaryOptions[0].id;
25570
- }
25571
26106
  function compactIssues(issues) {
25572
26107
  return issues.filter((issue$1) => issue$1 !== null);
25573
26108
  }
@@ -25618,7 +26153,6 @@ const repairActionsByIssueKind = {
25618
26153
  "image-surface-treatment-without-image-surface": ["fallback-image-surface-treatment"],
25619
26154
  "image-surface-motion-without-image-surface": ["fallback-image-surface-motion"],
25620
26155
  "hero-legibility-overridden-by-image-surface-treatment": ["remove-hero-legibility"],
25621
- "removed-transition-for-flush-frame": ["remove-transition"],
25622
26156
  "hero-image-without-legibility": ["fallback-hero-legibility"]
25623
26157
  };
25624
26158
  function repairActionsForIssue(issueKind) {
@@ -25706,11 +26240,6 @@ function repairDesignIssue(issue$1) {
25706
26240
  issue: issue$1,
25707
26241
  action: issue$1.allowedRepairActions[0]
25708
26242
  };
25709
- case "removed-transition-for-flush-frame": return {
25710
- kind: "repaired",
25711
- issue: issue$1,
25712
- action: issue$1.allowedRepairActions[0]
25713
- };
25714
26243
  case "hero-image-without-legibility": return {
25715
26244
  kind: "fallbackApplied",
25716
26245
  issue: issue$1,
@@ -25753,13 +26282,9 @@ function supportsBlockComposition(capability, composition) {
25753
26282
  default: return assertNever$1(composition);
25754
26283
  }
25755
26284
  }
25756
- function isDecorativeTransition(transition) {
25757
- return transition !== "none";
25758
- }
25759
26285
  const blockDesignFallback = {
25760
26286
  sectionSurface: "base",
25761
26287
  contentFrame: plainFrame,
25762
- transitionAfter: "none",
25763
26288
  sectionHeight: "default",
25764
26289
  heroLegibility: "none",
25765
26290
  imageSurfaceTreatment: { kind: "none" },
@@ -25775,7 +26300,6 @@ const appliedBlockDesignFallback = {
25775
26300
  appliedSectionSurface: blockDesignFallback.sectionSurface,
25776
26301
  appliedContentFrame: blockDesignFallback.contentFrame,
25777
26302
  appliedCardRole: null,
25778
- appliedTransitionAfter: blockDesignFallback.transitionAfter,
25779
26303
  appliedSectionHeight: blockDesignFallback.sectionHeight,
25780
26304
  appliedHeroLegibility: blockDesignFallback.heroLegibility,
25781
26305
  appliedImageSurfaceTreatment: blockDesignFallback.imageSurfaceTreatment,
@@ -25801,7 +26325,6 @@ function blockDesignRequest(block) {
25801
26325
  sectionSurface: block.sectionSurface,
25802
26326
  contentFrame: block.contentFrame,
25803
26327
  ...block.cardRole ? { cardRole: block.cardRole } : {},
25804
- transitionAfter: block.transitionAfter,
25805
26328
  sectionHeight: block.sectionHeight,
25806
26329
  heroLegibility: block.heroLegibility,
25807
26330
  imageSurfaceTreatment: block.imageSurfaceTreatment,
@@ -25820,7 +26343,6 @@ function resolveBlockDesignPlan(input) {
25820
26343
  let appliedSectionSurface = block.sectionSurface;
25821
26344
  let appliedContentFrame = block.contentFrame;
25822
26345
  const appliedCardRole = appliedCardRoleFor(block, capability);
25823
- let appliedTransitionAfter = block.transitionAfter;
25824
26346
  let appliedSectionHeight = block.sectionHeight ?? "default";
25825
26347
  let appliedHeroLegibility = block.heroLegibility ?? "none";
25826
26348
  let appliedImageSurfaceTreatment = block.imageSurfaceTreatment ?? { kind: "none" };
@@ -26085,7 +26607,6 @@ function resolveBlockDesignPlan(input) {
26085
26607
  appliedSectionSurface,
26086
26608
  appliedContentFrame,
26087
26609
  appliedCardRole,
26088
- appliedTransitionAfter,
26089
26610
  appliedSectionHeight,
26090
26611
  appliedHeroLegibility,
26091
26612
  appliedImageSurfaceTreatment,
@@ -26097,29 +26618,8 @@ function resolveBlockDesignPlan(input) {
26097
26618
  appliedComposition
26098
26619
  };
26099
26620
  });
26100
- const transitionSafeBlocks = blocks.map((block, index) => {
26101
- const nextBlock = blocks[index + 1];
26102
- if (!nextBlock || !isDecorativeTransition(block.appliedTransitionAfter) || block.appliedContentFrame.kind !== "flush-panel" && nextBlock.appliedContentFrame.kind !== "flush-panel") {
26103
- return block;
26104
- }
26105
- issues.push({
26106
- kind: "removed-transition-for-flush-frame",
26107
- blockId: block.blockId,
26108
- blockKind: block.blockKind,
26109
- purpose: block.purpose,
26110
- requested: block.appliedTransitionAfter,
26111
- fallback: "none",
26112
- policy: input.policy,
26113
- severity: designIssueSeverityForPolicy(input.policy),
26114
- allowedRepairActions: repairActionsForIssue("removed-transition-for-flush-frame")
26115
- });
26116
- return {
26117
- ...block,
26118
- appliedTransitionAfter: "none"
26119
- };
26120
- });
26121
26621
  return {
26122
- blocks: transitionSafeBlocks,
26622
+ blocks,
26123
26623
  issues
26124
26624
  };
26125
26625
  }
@@ -27006,80 +27506,6 @@ function recompileSiteChromeHeaderFromPersistedIntent(theme, siteStyle, requeste
27006
27506
  };
27007
27507
  }
27008
27508
 
27009
- //#endregion
27010
- //#region ../theme-core/src/site-styles/runtimeBackgroundResolution.ts
27011
- function parseCanonicalRuntimeBackground(content) {
27012
- const sectionStyles = readRecord$1(content._sectionStyles);
27013
- const background = readRecord$1(sectionStyles?.background);
27014
- if (!background) return { kind: "none" };
27015
- const parsed = boxBackgroundSchema.safeParse(background);
27016
- if (!parsed.success) return { kind: "none" };
27017
- return parsed.data.contentOwner === pageDesignEditorBackgroundOverrideOwner ? {
27018
- kind: "page-design-override",
27019
- background: parsed.data
27020
- } : {
27021
- kind: "block-authored",
27022
- background: parsed.data
27023
- };
27024
- }
27025
- function resolveSectionBackground(source, lookDefault) {
27026
- if (source.kind === "page-design-override") {
27027
- return {
27028
- kind: "page-design-override",
27029
- background: source.background
27030
- };
27031
- }
27032
- if (lookDefault) {
27033
- if (source.kind === "block-authored" && (source.background.type === "image" || source.background.type === "video")) {
27034
- return {
27035
- kind: "block-authored",
27036
- background: mergeLookForegroundWithAuthoredMedia(lookDefault, source.background)
27037
- };
27038
- }
27039
- return {
27040
- kind: "look-default",
27041
- background: lookDefault
27042
- };
27043
- }
27044
- if (source.kind === "block-authored" && (source.background.type === "image" || source.background.type === "video")) {
27045
- return {
27046
- kind: "block-authored",
27047
- background: source.background
27048
- };
27049
- }
27050
- return source.kind === "block-authored" ? {
27051
- kind: "block-authored",
27052
- background: source.background
27053
- } : { kind: "none" };
27054
- }
27055
- function resolveSectionForeground(background, palette) {
27056
- return resolveSurfaceForeground(background.kind === "none" ? null : background.background, palette, {}, { compositeBase: "background" });
27057
- }
27058
- /**
27059
- * Read foreground authority from post-compiler block content. The preview
27060
- * compiler has already materialized any Look-owned background into
27061
- * `_sectionStyles`; raw authored content must be composed with its Look through
27062
- * `resolveSectionBackground` instead.
27063
- */
27064
- function resolveCompiledSectionForegroundFromContent(content, palette) {
27065
- if (!content) return resolveSurfaceForeground(null, palette);
27066
- return resolveSectionForeground(resolveSectionBackground(parseCanonicalRuntimeBackground(content), null), palette);
27067
- }
27068
- function mergeLookForegroundWithAuthoredMedia(lookDefault, authored) {
27069
- const merged = boxBackgroundSchema.safeParse({
27070
- ...lookDefault,
27071
- type: authored.type,
27072
- image: authored.image,
27073
- objectFit: authored.objectFit,
27074
- scale: authored.scale,
27075
- position: authored.position
27076
- });
27077
- return merged.success ? merged.data : authored;
27078
- }
27079
- function readRecord$1(value) {
27080
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
27081
- }
27082
-
27083
27509
  //#endregion
27084
27510
  //#region ../theme-core/src/site-styles/previewRuntimeCapabilities.ts
27085
27511
  function cardRoleCapabilitiesByBlockKind(blocks) {
@@ -27555,7 +27981,6 @@ function compileEditorModelRuntime(input) {
27555
27981
  blockKind: block.blockKind,
27556
27982
  purpose: previewCustomizePurpose,
27557
27983
  ...look ? { appearancePresetId: look.id } : {},
27558
- transitionAfter: "none",
27559
27984
  emphasis: "medium",
27560
27985
  sectionSurface: "base",
27561
27986
  contentFrame: { kind: "plain" },
@@ -27565,30 +27990,31 @@ function compileEditorModelRuntime(input) {
27565
27990
  if (blocks.length === 0) {
27566
27991
  return {
27567
27992
  blocks: [],
27568
- boundaries: []
27993
+ joins: []
27569
27994
  };
27570
27995
  }
27571
- const fallbackTransitionAfterByBlockId = new Map(blocks.map((block) => [block.blockId, block.transitionAfter]));
27572
- const compiled = compilePageRuntimeFields({
27996
+ return compilePageRuntimeFields({
27573
27997
  resolved: resolveBlockDesignPlan({
27574
27998
  policy: "user",
27575
27999
  blocks,
27576
28000
  capabilities: runtimeCapabilitiesForBlocks(blocks, cardRoleCapabilitiesByBlockKind(input.editorModel.blocks))
27577
28001
  }),
28002
+ joins: input.editorModel.boundaries.flatMap(runtimeJoinForEditorBoundary),
27578
28003
  surfaceResolutionContext: input.surfaceResolutionContext,
27579
28004
  designRules: input.designRules,
27580
28005
  componentRules: input.componentRules,
27581
28006
  sectionSpacing: input.sectionSpacing
27582
28007
  });
27583
- return {
27584
- blocks: compiled.blocks,
27585
- boundaries: compileEditorBoundaryRuntime({
27586
- boundaries: input.editorModel.boundaries,
27587
- blocks: compiled.blocks,
27588
- fallbackTransitionAfterByPreviousBlockId: fallbackTransitionAfterByBlockId,
27589
- designRules: input.designRules
27590
- })
27591
- };
28008
+ }
28009
+ /** The editor boundary's effective break as an intent-only join; none for no break. */
28010
+ function runtimeJoinForEditorBoundary(boundary) {
28011
+ const option = boundary.availableBreaks.find((candidate$1) => candidate$1.id === boundary.breakChoice.effectiveValue);
28012
+ const treatment = option?.transitionAfter;
28013
+ return treatment && isDecorativeTransitionIntent(treatment) ? [{
28014
+ previousBlockId: boundary.previousBlockId,
28015
+ nextBlockId: boundary.nextBlockId,
28016
+ treatment
28017
+ }] : [];
27592
28018
  }
27593
28019
  function buildCompiledRuntimeFieldsByBlockId(compiled) {
27594
28020
  return new Map(compiled.blocks.map((block) => [block.blockId, block.fields]));
@@ -27596,31 +28022,6 @@ function buildCompiledRuntimeFieldsByBlockId(compiled) {
27596
28022
  function buildEditorModelRuntimeFieldsByBlockId(input) {
27597
28023
  return buildCompiledRuntimeFieldsByBlockId(compileEditorModelRuntime(input));
27598
28024
  }
27599
- function buildEditorModelRuntimeBoundaries(input) {
27600
- return compileEditorModelRuntime(input).boundaries;
27601
- }
27602
- function compileEditorBoundaryRuntime(input) {
27603
- const blockById = new Map(input.blocks.map((block) => [block.blockId, block]));
27604
- return input.boundaries.flatMap((boundary) => {
27605
- const previousBlock = blockById.get(boundary.previousBlockId);
27606
- const nextBlock = blockById.get(boundary.nextBlockId);
27607
- const transitionAfter = transitionAfterForEditorBoundary(boundary) ?? input.fallbackTransitionAfterByPreviousBlockId.get(boundary.previousBlockId) ?? null;
27608
- if (!previousBlock || !nextBlock || !transitionAfter) return [];
27609
- const compiled = compileBoundaryRuntime({
27610
- boundaryId: boundary.boundaryId,
27611
- previousBlockId: boundary.previousBlockId,
27612
- nextBlockId: boundary.nextBlockId,
27613
- treatment: transitionAfter,
27614
- fromSurface: previousBlock.visualSectionSurface,
27615
- toSurface: nextBlock.visualSectionSurface,
27616
- rules: input.designRules
27617
- });
27618
- return compiled ? [compiled] : [];
27619
- });
27620
- }
27621
- function transitionAfterForEditorBoundary(boundary) {
27622
- return boundary.availableBreaks.find((candidate$1) => candidate$1.id === boundary.breakChoice.effectiveValue)?.transitionAfter ?? null;
27623
- }
27624
28025
  function compileEditorModelRuntimeFieldsForBlock(input) {
27625
28026
  return buildEditorModelRuntimeFieldsByBlockId(input).get(input.blockId) ?? null;
27626
28027
  }
@@ -27631,54 +28032,6 @@ function applyCompiledRuntimeFieldsToContent(content, fields) {
27631
28032
  ...runtimeFields
27632
28033
  };
27633
28034
  }
27634
- function retargetCompiledRuntimeFieldsForContentAdjacency(fields, adjacency) {
27635
- if (!fields?._transitionStyles) return fields;
27636
- const transition = fields._transitionStyles;
27637
- const fromSurface = contentOwnedBackgroundEndpoint(adjacency.currentContent) ?? transition.from;
27638
- const toSurface = contentOwnedBackgroundEndpoint(adjacency.nextContent) ?? transition.to;
27639
- if (fromSurface === transition.from && toSurface === transition.to) {
27640
- return fields;
27641
- }
27642
- return {
27643
- ...fields,
27644
- _transitionStyles: compileTransitionRuntime({
27645
- treatment: transition.treatment,
27646
- from: fromSurface,
27647
- to: toSurface
27648
- })
27649
- };
27650
- }
27651
- function retargetCompiledBoundaryRuntimeForContentAdjacency(boundary, adjacency) {
27652
- const fromSurface = contentOwnedBackgroundEndpoint(adjacency.currentContent) ?? boundary.from;
27653
- const toSurface = contentOwnedBackgroundEndpoint(adjacency.nextContent) ?? boundary.to;
27654
- if (fromSurface === boundary.from && toSurface === boundary.to) {
27655
- return boundary;
27656
- }
27657
- const transition = compileTransitionRuntime({
27658
- treatment: boundary.treatment,
27659
- from: fromSurface,
27660
- to: toSurface
27661
- });
27662
- return {
27663
- ...boundary,
27664
- from: transition.from,
27665
- to: transition.to,
27666
- cssVars: transition.cssVars
27667
- };
27668
- }
27669
- function contentOwnedBackgroundEndpoint(content) {
27670
- const sectionStyles = readRecord(content?._sectionStyles);
27671
- const background = readRecord(sectionStyles?.background);
27672
- if (background?.contentOwner !== pageDesignEditorBackgroundOverrideOwner || background.type !== "color") {
27673
- return null;
27674
- }
27675
- const token = parseThemeColorToken(background.color);
27676
- if (!token) return null;
27677
- return {
27678
- kind: "color-token",
27679
- token
27680
- };
27681
- }
27682
28035
  function preserveContentOwnedBackgroundFields(content, fields) {
27683
28036
  const source = parseCanonicalRuntimeBackground(content);
27684
28037
  const resolved = resolveSectionBackground(source, fields._sectionStyles?.background);
@@ -27693,9 +28046,6 @@ function preserveContentOwnedBackgroundFields(content, fields) {
27693
28046
  }
27694
28047
  };
27695
28048
  }
27696
- function readRecord(value) {
27697
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
27698
- }
27699
28049
  function restoreCompiledRuntimeFieldsFromContent(currentContent, previousContent) {
27700
28050
  const nextContent = stripCompiledRuntimeStyleFields(currentContent);
27701
28051
  for (const fieldName of compiledRuntimeStyleFieldNames) {
@@ -27742,7 +28092,7 @@ function compileContentTemplateRuntimeFields(input) {
27742
28092
  }));
27743
28093
  return {
27744
28094
  blocks,
27745
- boundaries: compiled.boundaries,
28095
+ joins: compiled.joins,
27746
28096
  fieldsByBlockId: new Map(blocks.map((block) => [block.blockId, block.fields])),
27747
28097
  issues: editorModel.issues
27748
28098
  };
@@ -28149,9 +28499,6 @@ function defaultContentFrame() {
28149
28499
  function cardIntentFor(design, lookDefaults) {
28150
28500
  return { cardRole: valueOrLookDefault(design.cardRole, lookDefaults?.cardRole, "none") };
28151
28501
  }
28152
- function defaultTransition() {
28153
- return "none";
28154
- }
28155
28502
  function defaultEmphasis(block) {
28156
28503
  return isHeroBlock(block) ? "high" : "medium";
28157
28504
  }
@@ -28176,8 +28523,24 @@ function isDefaultLookSafeForStyle(preset, siteStyle) {
28176
28523
  }
28177
28524
  return preset.design.contentFrame.kind === "plain" && preset.design.cardRole === "none";
28178
28525
  }
28179
- function materializeBoundaryTransition(design, boundary, defaultValue) {
28180
- return valueOrDefault(boundary.transitionAfter, valueOrDefault(design.transitionAfter, defaultValue));
28526
+ /**
28527
+ * The generated pipeline's joins: candidates come from the generated intent
28528
+ * and `resolveJoinTreatment` decides. Joins that resolve to no break are
28529
+ * absent; whether one draws is composition's call.
28530
+ */
28531
+ function resolveGeneratedJoins(input) {
28532
+ const intentByBlockId = new Map(input.intent.blocks.map((block) => [block.blockId, block]));
28533
+ return input.resolved.blocks.flatMap((block, index) => {
28534
+ const next = input.resolved.blocks[index + 1];
28535
+ const blockIntent = intentByBlockId.get(block.blockId);
28536
+ if (!next || !blockIntent) return [];
28537
+ const { join } = resolveJoinTreatment({
28538
+ candidates: joinCandidatesFromGeneratedIntent(blockIntent),
28539
+ previousBlockId: block.blockId,
28540
+ nextBlockId: next.blockId
28541
+ });
28542
+ return join ? [join] : [];
28543
+ });
28181
28544
  }
28182
28545
  function materializeBlockDesignPlan(block, siteStyle) {
28183
28546
  const lookDefaults = defaultAppearanceDesignForBlock({
@@ -28194,7 +28557,6 @@ function materializeBlockDesignPlan(block, siteStyle) {
28194
28557
  sectionSurface,
28195
28558
  contentFrame: valueOrLookDefault(block.design.contentFrame, lookDefaults?.contentFrame, defaultContentFrame()),
28196
28559
  ...cardIntentFor(block.design, lookDefaults),
28197
- transitionAfter: materializeBoundaryTransition(block.design, block.boundaryIntent, defaultTransition()),
28198
28560
  emphasis: valueOrDefault(block.design.emphasis, defaultEmphasis(block)),
28199
28561
  heroLegibility: valueOrLookDefault(block.mediaIntent.heroLegibility, lookDefaults?.heroLegibility, defaultLegibility),
28200
28562
  ...lookDefaults?.sectionHeight ? { sectionHeight: lookDefaults.sectionHeight } : {},
@@ -28250,7 +28612,6 @@ function resolvedBlockToPlan(block) {
28250
28612
  sectionSurface: block.appliedSectionSurface,
28251
28613
  contentFrame: block.appliedContentFrame,
28252
28614
  ...block.appliedCardRole ? { cardRole: block.appliedCardRole } : {},
28253
- transitionAfter: block.appliedTransitionAfter,
28254
28615
  emphasis: block.emphasis,
28255
28616
  sectionHeight: block.appliedSectionHeight,
28256
28617
  heroLegibility: block.appliedHeroLegibility,
@@ -28387,10 +28748,15 @@ function runGeneratedPageDesignPipeline(input) {
28387
28748
  })
28388
28749
  };
28389
28750
  }
28751
+ const joins = resolveGeneratedJoins({
28752
+ intent,
28753
+ resolved
28754
+ });
28390
28755
  const compositionIssues = validateCompositionBudget({
28391
28756
  policy: "generated",
28392
28757
  budget: input.siteStyle.template.compositionBudget,
28393
28758
  resolved,
28759
+ joins,
28394
28760
  blockContexts: intent.blocks.map((block) => ({
28395
28761
  blockId: block.blockId,
28396
28762
  mediaIntent: block.mediaIntent.media,
@@ -28414,6 +28780,7 @@ function runGeneratedPageDesignPipeline(input) {
28414
28780
  }
28415
28781
  const compiledRuntime = compilePageRuntimeFields({
28416
28782
  resolved,
28783
+ joins,
28417
28784
  surfaceResolutionContext: surfaceResolutionContextFromTheme(compileTokenRecipes(input.siteStyle.template.tokenRecipes)),
28418
28785
  designRules: designRuntimeRulesForSiteStyle(input.siteStyle),
28419
28786
  componentRules: componentRuntimeRulesForSiteStyle(input.siteStyle)
@@ -28432,6 +28799,7 @@ function runGeneratedPageDesignPipeline(input) {
28432
28799
  intent,
28433
28800
  plan,
28434
28801
  resolved,
28802
+ joins,
28435
28803
  headerOpeningSectionContext: deriveHeaderOpeningSectionContext({ openingBlock: resolved.blocks[0] ?? null }),
28436
28804
  headerOpeningSectionRepairs: headerOpeningSectionPlan.repairs,
28437
28805
  repairs,
@@ -28678,9 +29046,7 @@ function isNavUnderlineEffect(effect) {
28678
29046
  }
28679
29047
  /** Concrete hex behind a plain palette token, or null when it is not one. */
28680
29048
  function paletteHexForToken(theme, token) {
28681
- const parts = parseColorTokenParts(token);
28682
- if (parts.kind !== "token" || parts.opacityPercent !== null) return null;
28683
- return expandPalette(theme.palette)[parts.token] ?? null;
29049
+ return opaquePaletteHexForToken(expandPalette(theme.palette), token);
28684
29050
  }
28685
29051
  function compileHeaderScrollTreatment(theme, recipe, foregroundToken) {
28686
29052
  if (recipe.scrollTreatment !== "contrast-glass") return undefined;
@@ -29822,14 +30188,26 @@ const paidOnlineServiceInputSchema = z.object({
29822
30188
  priceAmount: z.number().int().positive(),
29823
30189
  payment: z.object({ mode: z.literal("paid_online_intent") }).strict()
29824
30190
  }).strict();
30191
+ /**
30192
+ * A service input with one of the three category placements: none, a
30193
+ * `categoryRef` to a category upserted in the same batch, or the
30194
+ * `categoryIdentifier` of one that already exists (issue 1403).
30195
+ */
30196
+ function withAppointmentServiceCategoryPlacement(schema) {
30197
+ return z.union([
30198
+ schema.extend(noAppointmentServiceCategoryFields),
30199
+ schema.extend(appointmentServiceCategoryRefFields),
30200
+ schema.extend(appointmentServiceCategoryIdentifierFields)
30201
+ ]);
30202
+ }
29825
30203
  const upsertAppointmentServiceCommandSchema = z.object({
29826
30204
  type: z.literal("upsertAppointmentService"),
29827
30205
  ref: appointmentServiceRefSchema,
29828
- input: z.union([
29829
- manualRequestServiceInputSchema,
29830
- freeServiceInputSchema,
29831
- paidOnlineServiceInputSchema
29832
- ])
30206
+ input: z.unknown().superRefine(rejectBothAppointmentServiceCategoryKeys).pipe(z.union([
30207
+ withAppointmentServiceCategoryPlacement(manualRequestServiceInputSchema),
30208
+ withAppointmentServiceCategoryPlacement(freeServiceInputSchema),
30209
+ withAppointmentServiceCategoryPlacement(paidOnlineServiceInputSchema)
30210
+ ]))
29833
30211
  }).strict();
29834
30212
  const upsertBookableResourceCommandSchema = z.object({
29835
30213
  type: z.literal("upsertBookableResource"),
@@ -29881,6 +30259,31 @@ function validatePortalOrder(input, ctx) {
29881
30259
  });
29882
30260
  }
29883
30261
 
30262
+ //#endregion
30263
+ //#region ../site-commands/src/bookingCommandSchemaOptions.ts
30264
+ /**
30265
+ * The booking, entitlement and appointment command schemas, in the order the
30266
+ * site command union lists them. `configureBookingSettings` stays with the
30267
+ * site-level commands at the head of the union.
30268
+ */
30269
+ const bookingCommandSchemaOptions = [
30270
+ upsertBookingPassProductCommandSchema,
30271
+ upsertBookingMembershipProductCommandSchema,
30272
+ seedBookingCustomerCommandSchema,
30273
+ grantCustomerPassCommandSchema,
30274
+ grantCustomerMembershipCommandSchema,
30275
+ seedAppointmentBookingCommandSchema,
30276
+ seedEventSeriesRegistrationCommandSchema,
30277
+ seedEventRegistrationCommandSchema,
30278
+ seedPassRedemptionCommandSchema,
30279
+ seedPortalOrderCommandSchema,
30280
+ upsertAppointmentServiceCategoryCommandSchema,
30281
+ upsertAppointmentServiceCommandSchema,
30282
+ upsertBookableResourceCommandSchema,
30283
+ assignAppointmentServiceResourcesCommandSchema,
30284
+ configureAppointmentAvailabilityCommandSchema
30285
+ ];
30286
+
29884
30287
  //#endregion
29885
30288
  //#region ../site-commands/src/commands.ts
29886
30289
  const mediaRefSchema = resourceRefSchema("media");
@@ -30349,20 +30752,7 @@ const siteCommandSchema = z.discriminatedUnion("type", [
30349
30752
  deleteEventCategoryCommandSchema,
30350
30753
  upsertEventSeriesCommandSchema,
30351
30754
  deleteEventSeriesCommandSchema,
30352
- upsertBookingPassProductCommandSchema,
30353
- upsertBookingMembershipProductCommandSchema,
30354
- seedBookingCustomerCommandSchema,
30355
- grantCustomerPassCommandSchema,
30356
- grantCustomerMembershipCommandSchema,
30357
- seedAppointmentBookingCommandSchema,
30358
- seedEventSeriesRegistrationCommandSchema,
30359
- seedEventRegistrationCommandSchema,
30360
- seedPassRedemptionCommandSchema,
30361
- seedPortalOrderCommandSchema,
30362
- upsertAppointmentServiceCommandSchema,
30363
- upsertBookableResourceCommandSchema,
30364
- assignAppointmentServiceResourcesCommandSchema,
30365
- configureAppointmentAvailabilityCommandSchema
30755
+ ...bookingCommandSchemaOptions
30366
30756
  ]);
30367
30757
 
30368
30758
  //#endregion
@@ -30484,6 +30874,7 @@ const commandScopes = [
30484
30874
  "order",
30485
30875
  "booking_settings",
30486
30876
  "appointment_service",
30877
+ "appointment_service_category",
30487
30878
  "appointment_resource",
30488
30879
  "appointment_availability",
30489
30880
  "publish"
@@ -31112,6 +31503,19 @@ const siteCommandMetadata = {
31112
31503
  capability: "commerce.orders.write",
31113
31504
  support: "supported"
31114
31505
  },
31506
+ upsertAppointmentServiceCategory: {
31507
+ scope: "appointment_service_category",
31508
+ risk: "medium",
31509
+ writeSemantics: "upsert",
31510
+ lifecycleIntent: "mutation",
31511
+ idempotencyStrategy: "natural_key",
31512
+ conflictPolicy: "update_existing",
31513
+ previewability: "dry_run_only",
31514
+ allowedRuntimes: allRuntimes,
31515
+ intendedExposure: recipeExposure,
31516
+ capability: "bookings.appointment_service_categories.write",
31517
+ support: "supported"
31518
+ },
31115
31519
  upsertAppointmentService: {
31116
31520
  scope: "appointment_service",
31117
31521
  risk: "medium",
@@ -31291,6 +31695,7 @@ const bookingRefCommandTypeValues = [
31291
31695
  "seedEventRegistration",
31292
31696
  "seedPassRedemption",
31293
31697
  "seedPortalOrder",
31698
+ "upsertAppointmentServiceCategory",
31294
31699
  "upsertAppointmentService",
31295
31700
  "upsertBookableResource",
31296
31701
  "assignAppointmentServiceResources",
@@ -31304,8 +31709,12 @@ function bookingCommandRefContributions(command) {
31304
31709
  switch (command.type) {
31305
31710
  case "upsertBookingMembershipProduct":
31306
31711
  case "seedBookingCustomer":
31307
- case "upsertAppointmentService":
31712
+ case "upsertAppointmentServiceCategory":
31308
31713
  case "upsertBookableResource": return provided(command.ref);
31714
+ case "upsertAppointmentService": return providedWithRequirements(command.ref, command.input.categoryRef === undefined ? [] : [{
31715
+ ref: command.input.categoryRef,
31716
+ field: "input.categoryRef"
31717
+ }]);
31309
31718
  case "upsertBookingPassProduct": return {
31310
31719
  ...provided(command.ref),
31311
31720
  requires: command.input.appointmentScope?.mode === "selected_services" ? command.input.appointmentScope.serviceRefs.map((ref, index) => ({
@@ -31838,7 +32247,8 @@ function matchSiteCommand(command, match) {
31838
32247
  case "seedEventRegistration": return match(command);
31839
32248
  case "seedPassRedemption": return match(command);
31840
32249
  case "seedPortalOrder": return match(command);
31841
- case "upsertAppointmentService": return match(command);
32250
+ case "upsertAppointmentServiceCategory":
32251
+ case "upsertAppointmentService":
31842
32252
  case "upsertBookableResource": return match(command);
31843
32253
  case "assignAppointmentServiceResources": return match(command);
31844
32254
  case "configureAppointmentAvailability": return match(command);
@@ -32848,4 +33258,4 @@ function parseSdkEventRunUpsertInput(input, runIndex, options) {
32848
33258
  }
32849
33259
 
32850
33260
  //#endregion
32851
- export { generateBaseInteractiveCSS as $, createContentEntryRef as $n, NAV_LINK_LETTER_SPACING_MAP as $t, getGradientPreset as A, featuresFromHex as An, resolveTokenOrCssColorValue as At, generateCssVars as B, normalizeCardRole as Bn, featureCardCompositionTreatmentValues as Bt, retargetCompiledBoundaryRuntimeForContentAdjacency as C, resolveSurfaceForeground as Cn, MQ_SM_DOWN as Ct, compileTransitionRuntime as D, defaultThemeTokens as Dn, isCssColorLiteral as Dt, getCuratedSiteStyle as E, buildThemeTokens as En, headerVariantValues as Et, parseBackgroundAppearanceColorToken as F, surfaceForegroundStyle as Fn, defineBlockDesignCapability as Ft, resolvedButtonColourDeclarations as G, footerModeSchema as Gn, testimonialsCompositionTreatmentValues as Gt, PUBLIC_BUTTON_ROLES as H, mediaSchema as Hn, introCompositionTreatmentValues as Ht, defineBlockDesignMetadata as I, surfaceForegroundValue as In, defineFrameCapability as It, contextualButtonSurfaceStyles as J, unsafeAsOpaqueButtonColourToken as Jn, getComponentBorderWidthPx as Jt, resolvedButtonFocusDeclarations as K, VARIANT_ROLES as Kn, tileTreatmentValues as Kt, require_jsx_runtime as L, generateShadowValue$1 as Ln, defineNoBlockDesignCapability as Lt, boundaryRuntimeMetricsForTransition as M, oklchToHexGamut as Mn, asAppearancePresetId as Mt, backgroundAppearanceColorTokenNames as N, DEFAULT_SURFACE_TEXT_COLOR as Nn, asDesignBlockId as Nt, gradientToCss as O, ORDINARY_TEXT_MIN_CONTRAST_RATIO as On, resolveOnColorForThemeToken as Ot, backgroundAppearanceQuickColorTokenNames as P, surfaceForegroundDeclarations as Pn, assertNever$1 as Pt, generateEffectsCSS as Q, createCommandRef as Qn, NAV_LINK_BORDER_RADIUS_MAP as Qt, require_react as R, cardRoleSchema as Rn, eventListTreatmentValues as Rt, eventBookingOfferIntentSchema as S, textColorStyle as Sn, MQ_MD_DOWN as St, readHeaderNavFontSizePreference as T, onColorTokenName as Tn, MQ_TABLET_ONLY as Tt, generateButtonCss as U, mediaTypeDiscriminants as Un, makeBoundaryId as Ut, buttonStyleSelectionSchema as V, mediaIdentifierSchema as Vn, headingDecorationColorTokenValues as Vt, RESOLVED_BUTTON_DISABLED_DECLARATIONS as W, persistableMediaSchema as Wn, offeringsPreviewCompositionTreatmentValues as Wt, resolveContextualButtonRole as X, createBlockRef as Xn, parseBlockCustomCssRules as Xt, deriveButtonSurfaceContext as Y, parseBlockColorToken as Yn, getComponentShadowVar as Yt, generateDefaultButtonSystem as Z, createBlocksRef as Zn, parseCustomCssRules as Zt, entryIdentifierForCommand as _, backgroundColorStyle as _n, BREAKPOINT_LG as _t, classifySdkEventBookingOffer as a, INPUT_SPACING_MAP as an, createPageRef as ar, compileButtonColourStates as at, pagePathForCommand as b, mergeStyles as bn, BREAKPOINT_XL as bt, stableStringify as c, ACCORDION_SPACING_MAP as cn, refSlug as cr, expandPalette as ct, mapEventCommandContent as d, ICON_CONFIG as dn, boxBackgroundOverlaySchema as dt, NAV_LINK_PADDING_MAP as en, createEventCategoryRef as er, generateInteractiveChildInheritanceCSS as et, evaluateSiteCommandBatchExposure as f, ICON_SIZE_PX as fn, boxBackgroundSchema as ft, blockOrderIdentifiersForCommand as g, TokenResolver as gn, renderHeaderPaintTargetRules as gt, blockIdentifierForCommand as h, generateCardCss as hn, getHeaderPaintTargets as ht, classifySdkEventSchedule as i, INPUT_FONT_SIZE_MAP as in, createNavigationRef as ir, parseThemeForPersistence as it, parseGradientPresetId as j, hexToRgb as jn, pageDesignEditorBackgroundOverrideOwner as jt, GRADIENT_PRESET_IDS as k, contrastRatio as kn, resolveThemeColorReferenceValue as kt, createDryRunReport as l, ACCORDION_TRIGGER_FONT_SCALE as ln, slugPart as lr, getTextColorForBackground as lt, getSiteCommandMetadata as m, getDefaultCardVariants as mn, generateMobileOverlayBackdropFilterFix as mt, sdkEventOccurrenceInputSchema as n, NAV_LINK_TEXT_SIZE_MAP as nn, createFooterRef as nr, themeButtonClassName as nt, venueManagementFields as o, LABEL_FONT_SIZE_MAP as on, createSiteCommandSiteRef as or, formatButtonColourContractError as ot, evaluateSiteCommandExposure as p, generateCardCoreCss as pn, generateHeaderScrollTreatmentStyles as pt, contextualButtonSurfaceResetStyles as q, buttonVariantIdSchema as qn, visibleHeadingDecorationFamilyValues as qt, sdkEventUpsertOptionalFieldSchemas as r, FONT_WEIGHT_MAP$1 as rn, createFormRef as rr, themeButtonClassNameWithTrailingIcon as rt, sortRecord as s, ACCORDION_OPEN_ACCENT_THICKNESS as sn, createVenueRef as sr, generateShadowValue as st, classifySdkEventTiming as t, NAV_LINK_PADDING_X_MAP as tn, createEventSeriesRef as tr, parseThemeButtonVariantId as tt, parseAndPlanCommandBatchForTypes as u, ACCORDION_TRIGGER_LETTER_SPACING as un, __toESM as ur, themeSchema as ut, pageIdentifierForBlockCommand as v, borderColorStyle as vn, BREAKPOINT_MD as vt, mergeHeaderNavTypographyPreference as w, isOnColorBackgroundCandidateToken as wn, MQ_SM_UP as wt, hasConfiguredBookingVertical as x, rgbColorValue as xn, MQ_LG_UP as xt, pageIdentifierForCommand as y, cssVarValue as yn, BREAKPOINT_SM as yt, pickShadeTowardBackground as z, cardVariantRoleValues as zn, eyebrowTreatmentValues as zt };
33261
+ export { resolveContextualButtonRole as $, footerModeSchema as $n, getComponentBorderWidthPx as $t, boundaryLayout as A, surfaceToneFromForegroundToken as An, headerVariantValues as At, require_jsx_runtime as B, oklchToHexGamut as Bn, defineBlockDesignCapability as Bt, mergeHeaderNavTypographyPreference as C, borderColorStyle as Cn, BREAKPOINT_SM as Ct, GRADIENT_PRESET_IDS as D, textColorStyle as Dn, MQ_SM_DOWN as Dt, gradientToCss as E, rgbColorValue as En, MQ_MD_DOWN as Et, transitionCatalogueEntries as F, defaultThemeTokens as Fn, pageDesignEditorBackgroundOverrideOwner as Ft, PUBLIC_BUTTON_ROLES as G, generateShadowValue$1 as Gn, featureCardCompositionTreatmentValues as Gt, pickShadeTowardBackground as H, surfaceForegroundDeclarations as Hn, defineNoBlockDesignCapability as Ht, backgroundAppearanceColorTokenNames as I, ORDINARY_TEXT_MIN_CONTRAST_RATIO as In, stripStaleTransitionStyleGroup as It, resolvedButtonColourDeclarations as J, normalizeCardRole as Jn, makeBoundaryId as Jt, generateButtonCss as K, cardRoleSchema as Kn, headingDecorationColorTokenValues as Kt, backgroundAppearanceQuickColorTokenNames as L, contrastRatio as Ln, asAppearancePresetId as Lt, sectionFactsForContent as M, isOnColorBackgroundCandidateToken as Mn, resolveOnColorForThemeToken as Mt, boundaryRuntimeMetricsForDecorativeTransition as N, onColorTokenName as Nn, resolveThemeColorReferenceValue as Nt, getGradientPreset as O, textColorValue as On, MQ_SM_UP as Ot, anglePolygonCss as P, buildThemeTokens as Pn, resolveTokenOrCssColorValue as Pt, deriveButtonSurfaceContext as Q, persistableMediaSchema as Qn, visibleHeadingDecorationFamilyValues as Qt, parseBackgroundAppearanceColorToken as R, featuresFromHex as Rn, asDesignBlockId as Rt, eventBookingOfferIntentSchema as S, backgroundColorStyle as Sn, BREAKPOINT_MD as St, getCuratedSiteStyle as T, mergeStyles as Tn, MQ_LG_UP as Tt, generateCssVars as U, surfaceForegroundStyle as Un, eventListTreatmentValues as Ut, require_react as V, DEFAULT_SURFACE_TEXT_COLOR as Vn, defineFrameCapability as Vt, buttonStyleSelectionSchema as W, surfaceForegroundValue as Wn, eyebrowTreatmentValues as Wt, contextualButtonSurfaceResetStyles as X, mediaSchema as Xn, testimonialsCompositionTreatmentValues as Xt, resolvedButtonFocusDeclarations as Y, mediaIdentifierSchema as Yn, offeringsPreviewCompositionTreatmentValues as Yt, contextualButtonSurfaceStyles as Z, mediaTypeDiscriminants as Zn, tileTreatmentValues as Zt, entryIdentifierForCommand as _, ICON_SIZE_PX as _n, slugPart as _r, generateHeaderScrollTreatmentStyles as _t, classifySdkEventBookingOffer as a, NAV_LINK_PADDING_MAP as an, createBlocksRef as ar, themeButtonExtraClassesWithTrailingIcon as at, pagePathForCommand as b, generateCardCss as bn, renderHeaderPaintTargetRules as bt, stableStringify as c, FONT_WEIGHT_MAP$1 as cn, createEventCategoryRef as cr, parseThemeForPersistence as ct, mapEventCommandContent as d, LABEL_FONT_SIZE_MAP as dn, createFormRef as dr, generateShadowValue as dt, getComponentShadowVar as en, VARIANT_ROLES as er, generateDefaultButtonSystem as et, evaluateSiteCommandBatchExposure as f, ACCORDION_OPEN_ACCENT_THICKNESS as fn, createNavigationRef as fr, expandPalette as ft, blockOrderIdentifiersForCommand as g, ICON_CONFIG as gn, refSlug as gr, boxBackgroundSchema as gt, blockIdentifierForCommand as h, ACCORDION_TRIGGER_LETTER_SPACING as hn, createVenueRef as hr, boxBackgroundOverlaySchema as ht, classifySdkEventSchedule as i, NAV_LINK_LETTER_SPACING_MAP as in, createBlockRef as ir, parseThemeButtonVariantId as it, composeBoundary as j, resolveSurfaceForeground as jn, isCssColorLiteral as jt, parseGradientPresetId as k, selectLogoVariant as kn, MQ_TABLET_ONLY as kt, createDryRunReport as l, INPUT_FONT_SIZE_MAP as ln, createEventSeriesRef as lr, compileButtonColourStates as lt, getSiteCommandMetadata as m, ACCORDION_TRIGGER_FONT_SCALE as mn, createSiteCommandSiteRef as mr, themeSchema as mt, sdkEventOccurrenceInputSchema as n, parseCustomCssRules as nn, unsafeAsOpaqueButtonColourToken as nr, generateBaseInteractiveCSS as nt, venueManagementFields as o, NAV_LINK_PADDING_X_MAP as on, createCommandRef as or, themeButtonSizeClassName as ot, evaluateSiteCommandExposure as p, ACCORDION_SPACING_MAP as pn, createPageRef as pr, getTextColorForBackground as pt, RESOLVED_BUTTON_DISABLED_DECLARATIONS as q, cardVariantRoleValues as qn, introCompositionTreatmentValues as qt, sdkEventUpsertOptionalFieldSchemas as r, NAV_LINK_BORDER_RADIUS_MAP as rn, parseBlockColorToken as rr, generateInteractiveChildInheritanceCSS as rt, sortRecord as s, NAV_LINK_TEXT_SIZE_MAP as sn, createContentEntryRef as sr, themeButtonVariantClassNames as st, classifySdkEventTiming as t, parseBlockCustomCssRules as tn, buttonVariantIdSchema as tr, generateEffectsCSS as tt, parseAndPlanCommandBatchForTypes as u, INPUT_SPACING_MAP as un, createFooterRef as ur, formatButtonColourContractError as ut, pageIdentifierForBlockCommand as v, generateCardCoreCss as vn, __toESM as vr, generateMobileOverlayBackdropFilterFix as vt, readHeaderNavFontSizePreference as w, cssVarValue as wn, BREAKPOINT_XL as wt, hasConfiguredBookingVertical as x, TokenResolver as xn, BREAKPOINT_LG as xt, pageIdentifierForCommand as y, getDefaultCardVariants as yn, getHeaderPaintTargets as yt, defineBlockDesignMetadata as z, hexToRgb as zn, assertNever$1 as zt };