riverbankcms 0.4.2 → 0.5.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,15 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { At as ok, B as parseUtcIsoDateTimeString, Ct as unsafeAsSiteScopedStoragePath, D as mapNonEmptyArray, Dt as unsafeAsUploadSessionId, E as fromArray, Et as unsafeAsStoragePath, O as hasText, Ot as unsafeAsUserId, St as unsafeAsSiteId, Tt as unsafeAsStorageBucketName, Z as assertNever, _ as parseFooterCompositionV1, _t as unsafeAsPositiveByteCount, a as parseVerticalPresetId, at as unsafeAsBookingRequirementId, bt as unsafeAsSafeDownloadFilename, c as EVENT_PRESET_VALUES, dt as unsafeAsEventOfferingId, f as createSecret, ft as unsafeAsEventSeriesId, g as footerNavigationLayouts, gt as unsafeAsOriginalFilename, h as stableJsonStringify, ht as unsafeAsOccurrenceId, i as parseVerticalId, k as isPresent, kt as err, l as ORDINARY_EVENT_PRESET_VALUES, lt as unsafeAsDetectedMimeType, m as parseManagementKey, mt as unsafeAsMediaAssetId, n as canonicalizeConfiguredBookingVerticalConfig, nt as parseSiteId, o as resolveSiteVerticalBookingConfig, ot as unsafeAsBookingSessionId, p as exposeSecret, pt as unsafeAsFileChecksumSha256, st as unsafeAsDeclaredMimeType, ut as unsafeAsEntitlementId, vt as unsafeAsPositiveSeconds, w as isCanonicalRichTextDoc, xt as unsafeAsSiteFontId, yt as unsafeAsProductId } from "./src-BRAF1Dee.mjs";
3
- import { a as classifySdkEventPricing, n as sdkEventOccurrenceInputSchema, o as classifySdkEventPaymentTerms } from "./sdk-event-upsert-C6zsxDcf.mjs";
4
- import { t as CliSdkPeerError } from "./sdk-peer-C6kOODEY.mjs";
2
+ import { At as ok, B as parseUtcIsoDateTimeString, Ct as unsafeAsSiteScopedStoragePath, D as mapNonEmptyArray, Dt as unsafeAsUploadSessionId, E as fromArray, Et as unsafeAsStoragePath, O as hasText, Ot as unsafeAsUserId, St as unsafeAsSiteId, Tt as unsafeAsStorageBucketName, Z as assertNever, _ as parseFooterCompositionV1, _t as unsafeAsPositiveByteCount, a as parseVerticalPresetId, at as unsafeAsBookingRequirementId, bt as unsafeAsSafeDownloadFilename, c as EVENT_PRESET_VALUES, dt as unsafeAsEventOfferingId, f as createSecret, ft as unsafeAsEventSeriesId, g as footerNavigationLayouts, gt as unsafeAsOriginalFilename, h as stableJsonStringify, ht as unsafeAsOccurrenceId, i as parseVerticalId, k as isPresent, kt as err, l as ORDINARY_EVENT_PRESET_VALUES, lt as unsafeAsDetectedMimeType, m as parseManagementKey, mt as unsafeAsMediaAssetId, n as canonicalizeConfiguredBookingVerticalConfig, nt as parseSiteId, o as resolveSiteVerticalBookingConfig, ot as unsafeAsBookingSessionId, p as exposeSecret, pt as unsafeAsFileChecksumSha256, st as unsafeAsDeclaredMimeType, ut as unsafeAsEntitlementId, vt as unsafeAsPositiveSeconds, w as isCanonicalRichTextDoc, xt as unsafeAsSiteFontId, yt as unsafeAsProductId } from "./src-DKm0bLEA.mjs";
3
+ import { a as classifySdkEventPricing, n as sdkEventOccurrenceInputSchema, o as classifySdkEventPaymentTerms } from "./sdk-event-upsert-Dy08IXYL.mjs";
4
+ import { t as CliSdkPeerError } from "./sdk-peer-sZF43pDS.mjs";
5
5
  import "node:module";
6
6
  import { Command } from "commander";
7
7
  import { AsyncLocalStorage } from "node:async_hooks";
8
8
  import * as fs from "fs/promises";
9
- import * as path from "path";
9
+ import * as path$1 from "path";
10
10
  import * as readline from "readline";
11
11
  import { z } from "zod";
12
- import { createHash } from "node:crypto";
12
+ import { constants } from "node:fs";
13
+ import { basename, dirname as dirname$1, join } from "node:path";
14
+ import { createHash, randomUUID } from "node:crypto";
15
+ import { access, chmod, link, lstat, mkdir, open, rename, unlink as unlink$1 } from "node:fs/promises";
13
16
 
14
17
  //#region rolldown:runtime
15
18
  var __create = Object.create;
@@ -175,11 +178,11 @@ function sdkCommandApplicationResultSchema(value) {
175
178
 
176
179
  //#endregion
177
180
  //#region ../api/src/sdkOccurrenceResponseSchemas.ts
178
- const identifierSchema = z.string().min(1);
181
+ const identifierSchema$1 = z.string().min(1);
179
182
  const timestampSchema = z.string().min(1);
180
183
  const sdkManagementEventOccurrenceSchema = z.object({
181
- id: identifierSchema.transform(unsafeAsOccurrenceId),
182
- seriesId: identifierSchema.transform(unsafeAsEventSeriesId),
184
+ id: identifierSchema$1.transform(unsafeAsOccurrenceId),
185
+ seriesId: identifierSchema$1.transform(unsafeAsEventSeriesId),
183
186
  startsAt: timestampSchema,
184
187
  endsAt: timestampSchema,
185
188
  timeZone: z.string().min(1),
@@ -214,6 +217,125 @@ const sdkEventOccurrenceResponseSchema = z.object({ occurrence: sdkManagementEve
214
217
  const sdkDeleteEventOccurrenceResponseSchema = z.object({ deleted: z.literal(true) });
215
218
  const sdkReorderResponseSchema = z.object({ reordered: z.literal(true) });
216
219
 
220
+ //#endregion
221
+ //#region ../api/src/sdkMediaResponseSchemas.ts
222
+ const identifierSchema = z.string().min(1);
223
+ const nullablePathSchema = z.string().nullable();
224
+ const managementMediaAssetSchema = z.object({
225
+ id: identifierSchema,
226
+ siteId: identifierSchema,
227
+ identifier: identifierSchema,
228
+ storageBucket: identifierSchema,
229
+ storagePath: identifierSchema,
230
+ filename: identifierSchema,
231
+ mimeType: identifierSchema,
232
+ width: z.number().int().nonnegative().nullable(),
233
+ height: z.number().int().nonnegative().nullable(),
234
+ bytes: z.number().int().nonnegative().nullable(),
235
+ checksumSha256: z.string().nullable(),
236
+ createdAt: identifierSchema,
237
+ updatedAt: identifierSchema
238
+ });
239
+ const blockLocationFields = {
240
+ siteId: identifierSchema,
241
+ blockId: identifierSchema,
242
+ blockKind: identifierSchema,
243
+ blockIdentifier: z.string().nullable(),
244
+ stage: z.enum(["draft", "published"])
245
+ };
246
+ const mediaUsageLocationSchema = z.discriminatedUnion("kind", [
247
+ z.object({
248
+ kind: z.literal("pageBlock"),
249
+ ...blockLocationFields,
250
+ pageId: identifierSchema,
251
+ pageTitle: z.string().nullable(),
252
+ pageDraftPath: nullablePathSchema,
253
+ pagePublishedPath: nullablePathSchema
254
+ }),
255
+ z.object({
256
+ kind: z.literal("entrySlotBlock"),
257
+ ...blockLocationFields,
258
+ entryId: identifierSchema,
259
+ entryTitle: z.string(),
260
+ typeKey: identifierSchema,
261
+ routeDraftPath: nullablePathSchema,
262
+ routePublishedPath: nullablePathSchema,
263
+ slotKey: identifierSchema
264
+ }),
265
+ z.object({
266
+ kind: z.literal("surfaceSlotBlock"),
267
+ ...blockLocationFields,
268
+ pageSurfaceId: identifierSchema,
269
+ surfaceSlotId: identifierSchema,
270
+ slotKey: identifierSchema
271
+ }),
272
+ z.object({
273
+ kind: z.literal("contentEntry"),
274
+ siteId: identifierSchema,
275
+ entryId: identifierSchema,
276
+ entryTitle: z.string(),
277
+ typeKey: identifierSchema,
278
+ routeDraftPath: nullablePathSchema,
279
+ routePublishedPath: nullablePathSchema,
280
+ stage: z.enum(["draft", "published"])
281
+ }),
282
+ z.object({
283
+ kind: z.literal("productFileDownload"),
284
+ siteId: identifierSchema,
285
+ productId: identifierSchema,
286
+ productTitle: z.string()
287
+ })
288
+ ]);
289
+ const mediaUsageItemSchema = z.object({
290
+ assetId: identifierSchema,
291
+ filename: z.string().nullable(),
292
+ usedCount: z.number().int().nonnegative(),
293
+ locations: z.array(mediaUsageLocationSchema)
294
+ });
295
+ const mediaDeleteWarningSchema = z.discriminatedUnion("kind", [z.object({
296
+ kind: z.literal("ORPHAN_CLEANUP_FAILED"),
297
+ message: z.string(),
298
+ storageBucket: identifierSchema,
299
+ storagePath: identifierSchema
300
+ }), z.object({
301
+ kind: z.literal("LABEL_CLEANUP_FAILED"),
302
+ message: z.string()
303
+ })]);
304
+ const sdkMediaListResponseSchema = z.object({
305
+ items: z.array(managementMediaAssetSchema),
306
+ total: z.number().int().nonnegative(),
307
+ limit: z.number().int().positive(),
308
+ offset: z.number().int().nonnegative(),
309
+ nextOffset: z.number().int().nonnegative().optional()
310
+ });
311
+ const sdkMediaDownloadResponseSchema = z.object({
312
+ transferUrl: z.string().url(),
313
+ asset: managementMediaAssetSchema
314
+ });
315
+ const sdkMediaDeletePreviewResponseSchema = z.discriminatedUnion("kind", [
316
+ z.object({ kind: z.literal("missing") }),
317
+ z.object({
318
+ kind: z.literal("in_use"),
319
+ usage: mediaUsageItemSchema
320
+ }),
321
+ z.object({
322
+ kind: z.literal("deletable"),
323
+ assetId: identifierSchema
324
+ })
325
+ ]);
326
+ const sdkMediaDeleteApplyResponseSchema = z.discriminatedUnion("kind", [
327
+ z.object({ kind: z.literal("missing") }),
328
+ z.object({
329
+ kind: z.literal("in_use"),
330
+ usage: mediaUsageItemSchema
331
+ }),
332
+ z.object({ kind: z.literal("deleted") }),
333
+ z.object({
334
+ kind: z.literal("deleted_with_warnings"),
335
+ warnings: z.tuple([mediaDeleteWarningSchema], mediaDeleteWarningSchema)
336
+ })
337
+ ]);
338
+
217
339
  //#endregion
218
340
  //#region ../blocks/src/system/types/link.ts
219
341
  const nullableString = z.union([z.string().min(1), z.null()]);
@@ -285,16 +407,16 @@ function visitLinkValueReferences(value, visitor) {
285
407
  if (!parsed.success) {
286
408
  return;
287
409
  }
288
- const link = parsed.data;
289
- switch (link.kind) {
410
+ const link$1 = parsed.data;
411
+ switch (link$1.kind) {
290
412
  case "internal":
291
- visitor.routeIds.add(link.routeId);
413
+ visitor.routeIds.add(link$1.routeId);
292
414
  return;
293
415
  case "page":
294
- visitor.pageIdentifiers.add(link.identifier);
416
+ visitor.pageIdentifiers.add(link$1.identifier);
295
417
  return;
296
418
  case "entry":
297
- visitor.entryIdentifiers.add(link.contentType, link.identifier);
419
+ visitor.entryIdentifiers.add(link$1.contentType, link$1.identifier);
298
420
  return;
299
421
  case "external":
300
422
  case "url": return;
@@ -9249,6 +9371,11 @@ const gradientConfigSchema = z.object({
9249
9371
  position: z.string().optional(),
9250
9372
  stops: z.array(gradientStopSchema).min(2)
9251
9373
  });
9374
+ const gradientsSchema = z.object({
9375
+ button: z.string().min(1).nullable().optional(),
9376
+ hero: z.string().min(1).nullable().optional(),
9377
+ background: z.string().min(1).nullable().optional()
9378
+ });
9252
9379
 
9253
9380
  //#endregion
9254
9381
  //#region ../theme-core/src/interactive/baseSchema.ts
@@ -10254,12 +10381,8 @@ const legacyHeaderVariantValues = Object.keys(legacyHeaderVariants);
10254
10381
  * `@riverbankcms/api` is `z.input<typeof themeSchema>`, so a widened input here
10255
10382
  * would leak straight into a public type.
10256
10383
  *
10257
- * Not every read of a persisted theme goes through the schema:
10258
- * `resolveRuntimeTheme` in
10259
- * `apps/dashboard/src/server/services/sites/runtimeSiteResponse.ts` returns the
10260
- * raw database JSON asserted as `Theme` with no parse, so a theme still
10261
- * carrying a legacy spelling reaches the theme editor un-aliased. That gap
10262
- * predates this alias and is harmless while persisted usage is zero.
10384
+ * Persisted runtime themes are admitted through `parseThemeForPersistence`
10385
+ * before reaching consumers, so legacy spellings normalize at that boundary.
10263
10386
  */
10264
10387
  const headerVariantWithLegacyAliasSchema = z.enum([...headerVariantValues, ...legacyHeaderVariantValues]).transform((value) => value in legacyHeaderVariants ? legacyHeaderVariants[value] : value);
10265
10388
 
@@ -10478,12 +10601,33 @@ const headerContainerSchema = z.object({
10478
10601
 
10479
10602
  //#endregion
10480
10603
  //#region ../theme-core/src/header/interactionPalette.ts
10481
- const headerInteractionPaletteValues = [
10604
+ /**
10605
+ * The palettes that fill the nav link with a colour on hover and focus. Each
10606
+ * one names a background/foreground token pair.
10607
+ */
10608
+ const headerInteractionPillPaletteValues = [
10482
10609
  "primary",
10483
10610
  "secondary",
10484
10611
  "accent"
10485
10612
  ];
10613
+ /**
10614
+ * "No pill": the nav link keeps whatever hover treatment its recipe compiled,
10615
+ * and the palette step writes nothing at all (#1318 Phase 4c decision 2).
10616
+ *
10617
+ * It exists because "a colour change on hover, no filled background" was not
10618
+ * expressible in the vocabulary, and it is what
10619
+ * `anna-raven-therapy-and-yoga` and `kirtan-lucy` have rendered since before
10620
+ * the palette existed. Without it those two rows can only be classified as a
10621
+ * conscious visual change.
10622
+ */
10623
+ const headerInteractionQuietPalette = "quiet";
10624
+ const headerInteractionPaletteValues = [...headerInteractionPillPaletteValues, headerInteractionQuietPalette];
10486
10625
  const headerInteractionPaletteSchema = z.enum(headerInteractionPaletteValues);
10626
+ /**
10627
+ * Narrowed to the pill palettes on purpose: `'quiet'` has no token pair, and a
10628
+ * caller that reaches this function without excluding it first is a type error
10629
+ * rather than a runtime surprise.
10630
+ */
10487
10631
  function headerInteractionPalettePair(palette) {
10488
10632
  switch (palette) {
10489
10633
  case "primary": return {
@@ -10883,11 +11027,12 @@ const inputStyle = z.object({
10883
11027
  ]),
10884
11028
  label: z.enum(["inside", "above"])
10885
11029
  });
10886
- const headerPositioning = z.enum([
11030
+ const headerPositioningValues = [
10887
11031
  "static",
10888
11032
  "sticky",
10889
11033
  "fixed"
10890
- ]);
11034
+ ];
11035
+ const headerPositioning = z.enum(headerPositioningValues);
10891
11036
  const headerNavStyle = z.enum([
10892
11037
  "minimal",
10893
11038
  "underline",
@@ -10904,7 +11049,6 @@ const navFontWeight = z.enum([
10904
11049
  "bold"
10905
11050
  ]);
10906
11051
  const headerMaxWidth = z.enum(["container", "full"]);
10907
- const headerBarFrame = z.enum(["full-bleed", "inset-shell"]);
10908
11052
  const headerBorderSchema = componentBorderSchema.extend({ position: z.enum([
10909
11053
  "bottom",
10910
11054
  "top",
@@ -10933,12 +11077,6 @@ const headerLogoTextSchema = z.object({
10933
11077
  ]).optional(),
10934
11078
  hideOnShrink: z.boolean().optional()
10935
11079
  }).optional();
10936
- const navEffectsSchema = z.object({
10937
- underlineGradient: z.boolean().default(false),
10938
- glow: z.boolean().default(false),
10939
- glowColor: z.string().optional(),
10940
- neumorphic: z.boolean().default(false)
10941
- }).optional();
10942
11080
  const dropdownStyleSchema = z.object({
10943
11081
  background: themeColorTokenSchema.default("surface"),
10944
11082
  textColor: themeColorTokenSchema.default("text"),
@@ -11029,10 +11167,8 @@ const headerSchema = z.object({
11029
11167
  variant: headerVariantWithLegacyAliasSchema,
11030
11168
  positioning: headerPositioning,
11031
11169
  shrinkOnScroll: z.boolean(),
11032
- maxWidth: headerMaxWidth,
11033
- barFrame: headerBarFrame.optional(),
11170
+ maxWidth: headerMaxWidth.default("container"),
11034
11171
  scrollTreatment: headerScrollTreatmentSchema,
11035
- logoOverride: mediaSchema.nullable().optional(),
11036
11172
  background: headerBackgroundSchema.default({
11037
11173
  type: "color",
11038
11174
  color: "surface"
@@ -11044,13 +11180,11 @@ const headerSchema = z.object({
11044
11180
  navFontRole: z.enum(["body", "heading"]).optional(),
11045
11181
  navTextSize: navLinkStyleSchema.shape.textSize,
11046
11182
  interactionPalette: headerInteractionPaletteSchema.optional(),
11047
- navPlacement: z.enum(["end", "center"]).optional(),
11048
11183
  container: headerContainerSchema,
11049
11184
  border: headerBorderSchema,
11050
11185
  shadow: componentShadowSchema.optional(),
11051
11186
  logo: logoStyleSchema,
11052
11187
  logoText: headerLogoTextSchema,
11053
- navEffects: navEffectsSchema,
11054
11188
  navContainer: navContainerSchema,
11055
11189
  navLinkStyle: navLinkStyleSchema.optional(),
11056
11190
  ctaGap: headerCtaGapSchema.optional(),
@@ -11227,11 +11361,6 @@ const bodyTextSchema = z.object({ intro: z.object({
11227
11361
  paragraphSize: bodyTextIntroTypographySizeSchema.optional(),
11228
11362
  headingSize: bodyTextIntroTypographySizeSchema.optional()
11229
11363
  }).optional() }).optional();
11230
- const gradientsSchema = z.object({
11231
- button: z.string().min(1).nullable().optional(),
11232
- hero: z.string().min(1).nullable().optional(),
11233
- background: z.string().min(1).nullable().optional()
11234
- });
11235
11364
  /**
11236
11365
  * Testimonial quote-mark treatment, owned by the site style (analogous to the
11237
11366
  * accordion icon). `style` is the coarse selector; the optional fields fine-tune
@@ -11847,7 +11976,7 @@ function resolveAccessibleSurface(input) {
11847
11976
  threshold,
11848
11977
  rejectedCandidates
11849
11978
  });
11850
- const link = resolveRequiredForeground({
11979
+ const link$1 = resolveRequiredForeground({
11851
11980
  role: "link",
11852
11981
  candidates: recipe.linkCandidates,
11853
11982
  backgroundToken,
@@ -11856,7 +11985,7 @@ function resolveAccessibleSurface(input) {
11856
11985
  threshold,
11857
11986
  rejectedCandidates
11858
11987
  });
11859
- if (!heading || !text || !link) continue;
11988
+ if (!heading || !text || !link$1) continue;
11860
11989
  const supporting = resolveSupportingForeground({
11861
11990
  candidates: recipe.supportingCandidates,
11862
11991
  backgroundToken,
@@ -11876,7 +12005,7 @@ function resolveAccessibleSurface(input) {
11876
12005
  },
11877
12006
  heading,
11878
12007
  text,
11879
- link,
12008
+ link: link$1,
11880
12009
  supporting,
11881
12010
  rejectedCandidates
11882
12011
  };
@@ -12638,7 +12767,7 @@ const blockAppearancePresets = [
12638
12767
  id: "schedule-details-brand-depth",
12639
12768
  target: "events",
12640
12769
  label: "Brand-depth schedule details",
12641
- blockKinds: ["block.event-details", "block.course-details"],
12770
+ blockKinds: ["block.event-details"],
12642
12771
  design: {
12643
12772
  sectionSurface: "brand-depth-band",
12644
12773
  contentFrame: plainFrame,
@@ -13318,6 +13447,32 @@ function replaceSiteStyleCompilationPlan(input) {
13318
13447
  footerLookId: input.footerLookId ?? null
13319
13448
  };
13320
13449
  }
13450
+ /**
13451
+ * Writes the brand display / logo size preference onto the header's logo style.
13452
+ *
13453
+ * Moved here from preview-next in #1318 Phase 4a so the header preference
13454
+ * pipeline can be composed once, in theme-core, rather than half in the client.
13455
+ */
13456
+ function withHeaderBrandThemePreferences(theme, preferences) {
13457
+ const currentLogo = theme.header.logo;
13458
+ if (!currentLogo && preferences.brandDisplay === defaultHeaderBrandPreferences.brandDisplay && preferences.logoSize === defaultHeaderBrandPreferences.logoSize) {
13459
+ return theme;
13460
+ }
13461
+ return {
13462
+ ...theme,
13463
+ header: {
13464
+ ...theme.header,
13465
+ logo: {
13466
+ fontFamily: currentLogo?.fontFamily ?? "heading",
13467
+ letterSpacing: currentLogo?.letterSpacing ?? "normal",
13468
+ gradient: currentLogo?.gradient ?? false,
13469
+ ...currentLogo ?? {},
13470
+ brandDisplay: preferences.brandDisplay,
13471
+ size: preferences.logoSize
13472
+ }
13473
+ }
13474
+ };
13475
+ }
13321
13476
 
13322
13477
  //#endregion
13323
13478
  //#region ../theme-core/src/site-styles/schemaVersionedJson.ts
@@ -16360,90 +16515,6 @@ function deriveHeaderLogoShapeFromDimensions({ width, height }) {
16360
16515
  }
16361
16516
  const defaultHeaderLogoContext = { shape: "text-only" };
16362
16517
 
16363
- //#endregion
16364
- //#region ../theme-core/src/site-styles/headerInteractionPalette.ts
16365
- const clearSplitInteractionPalette = defineLookOptionGroup({
16366
- id: "interaction-palette",
16367
- label: "Hover colour",
16368
- defaultOptionId: "accent",
16369
- options: [
16370
- {
16371
- id: "accent",
16372
- label: "Accent"
16373
- },
16374
- {
16375
- id: "primary",
16376
- label: "Primary"
16377
- },
16378
- {
16379
- id: "secondary",
16380
- label: "Secondary"
16381
- }
16382
- ]
16383
- });
16384
- function resolveHeaderInteractionPaletteControl(group, preference) {
16385
- if (!group) return { kind: "unavailable" };
16386
- if (preference === "recommended") {
16387
- const resolution$1 = resolveLookOption(group, undefined);
16388
- return availableControlState(group, preference, resolution$1.option.id, null);
16389
- }
16390
- const resolution = resolveHeaderInteractionPalette(group, preference);
16391
- return availableControlState(group, preference, resolution.option.id, resolution.kind === "repaired" ? {
16392
- requested: preference,
16393
- fallback: resolution.option.id,
16394
- reason: resolution.reason
16395
- } : null);
16396
- }
16397
- function availableControlState(group, preference, effectivePalette, repair$1) {
16398
- return {
16399
- kind: "available",
16400
- preference,
16401
- effectivePalette,
16402
- options: [{
16403
- value: "recommended",
16404
- label: `Recommended (${defaultOptionLabel(group)})`
16405
- }, ...group.options.map((option) => ({
16406
- value: option.id,
16407
- label: option.label
16408
- }))],
16409
- repair: repair$1
16410
- };
16411
- }
16412
- function applyHeaderInteractionPalette(header, group, preference) {
16413
- if (!group) return header;
16414
- if (!header.navLinkStyle) {
16415
- throw new Error(`Header interaction palette "${group.id}" requires a nav link style.`);
16416
- }
16417
- const palette = resolveHeaderInteractionPalette(group, preference).option.id;
16418
- const pair = headerInteractionPalettePair(palette);
16419
- const pillEffect = {
16420
- effectId: "background-on-hover",
16421
- options: {
16422
- colorToken: pair.backgroundToken,
16423
- textColorToken: pair.foregroundToken
16424
- }
16425
- };
16426
- return {
16427
- ...header,
16428
- navLinkStyle: {
16429
- ...header.navLinkStyle,
16430
- hoverColorToken: null,
16431
- borderRadius: "full",
16432
- effects: {
16433
- ...header.navLinkStyle.effects,
16434
- hover: [pillEffect],
16435
- focus: [pillEffect]
16436
- }
16437
- }
16438
- };
16439
- }
16440
- function resolveHeaderInteractionPalette(group, preference) {
16441
- return resolveLookOption(group, preference === "recommended" ? undefined : preference);
16442
- }
16443
- function defaultOptionLabel(group) {
16444
- return resolveLookOption(group, undefined).option.label;
16445
- }
16446
-
16447
16518
  //#endregion
16448
16519
  //#region ../theme-core/src/site-styles/headerLayoutCatalog.ts
16449
16520
  const titleOnlyLogoAccommodation = {
@@ -16564,6 +16635,62 @@ const overlayLogoAccommodations = [
16564
16635
  inlineMarkWithTitleLogoAccommodation,
16565
16636
  unbackedOverlayLogoAccommodation
16566
16637
  ];
16638
+ const allCuratedLogoAccommodations = [
16639
+ ...inlineClassicLogoAccommodations,
16640
+ ...centeredLogoAccommodations,
16641
+ ...overlayLogoAccommodations
16642
+ ];
16643
+ /**
16644
+ * Whether an accommodation pattern draws its own backing behind the logo.
16645
+ * Derived from the catalogue above rather than restated, so a new backing
16646
+ * accommodation cannot drift out of sync with this answer. Patterns declared in
16647
+ * `LogoAccommodationPattern` but not offered by any layout resolve to `false`,
16648
+ * which is the honest answer: nothing can select them.
16649
+ */
16650
+ const backingLogoAccommodationPatterns = new Set(allCuratedLogoAccommodations.filter((option) => option.providesBacking === true).map((option) => option.pattern));
16651
+ function logoAccommodationProvidesBacking(pattern) {
16652
+ return backingLogoAccommodationPatterns.has(pattern);
16653
+ }
16654
+ /**
16655
+ * The single answer to "what shape is this site's logo" (#1318 Phase 4b).
16656
+ *
16657
+ * The server and the public frontend each had their own copy of this, and they
16658
+ * disagreed on the one case that matters: a logo whose dimensions are unknown.
16659
+ * The server assumed `horizontal`; the frontend asked whether the persisted
16660
+ * accommodation brings its own backing and answered `square` if it does. The
16661
+ * richer rule is the correct one — a badge or masthead accommodation is
16662
+ * evidence about the asset that raw dimensions would otherwise supply — and it
16663
+ * reduces to `horizontal` whenever there is no backing, so it subsumes the
16664
+ * server's rule rather than contradicting it. The backing question goes through
16665
+ * `logoAccommodationProvidesBacking`, so the pattern list has one home.
16666
+ */
16667
+ function deriveHeaderLogoShape({ asset, accommodation }) {
16668
+ if (!asset) return "text-only";
16669
+ if (!asset.width || !asset.height) {
16670
+ return accommodation !== undefined && logoAccommodationProvidesBacking(accommodation) ? "square" : "horizontal";
16671
+ }
16672
+ return deriveHeaderLogoShapeFromDimensions(asset);
16673
+ }
16674
+ /**
16675
+ * The site's logo as the header pipeline sees it, from the two database values
16676
+ * that describe it: the asset (or its absence) and the accommodation the row
16677
+ * currently persists, which is evidence about the asset when its dimensions are
16678
+ * missing.
16679
+ *
16680
+ * One definition, shared by every shell that has to answer it — the dashboard's
16681
+ * `loadHeaderLogoContextForSite`, the public frontend, and the Phase 4c repair
16682
+ * generator — because a second copy is a second answer to "what shape is this
16683
+ * site's logo", and the two former copies disagreed on exactly the case this
16684
+ * one is careful about (#1318 Phase 4b/4c).
16685
+ */
16686
+ function headerLogoShapeContext(input) {
16687
+ return {
16688
+ kind: "shape",
16689
+ shape: deriveHeaderLogoShape(input),
16690
+ ...input.brand?.brandDisplay === undefined ? {} : { brandDisplayMode: input.brand.brandDisplay },
16691
+ ...input.brand?.logoSize === undefined ? {} : { sizePolicy: input.brand.logoSize }
16692
+ };
16693
+ }
16567
16694
  function logoFactsForHeaderContext(logo) {
16568
16695
  if (logo.kind === "facts") return logo.facts;
16569
16696
  switch (logo.shape) {
@@ -16604,22 +16731,6 @@ function requireHeaderBrandPresentationSelection(plan) {
16604
16731
  if (plan.kind === "selected") return plan;
16605
16732
  throw new Error(`Selected header layout did not have a compatible logo accommodation: ${plan.reasons.join(", ")}`);
16606
16733
  }
16607
- /**
16608
- * Starts each compilation from the generated theme's header branch, then
16609
- * clears optional nested styling fields that would otherwise leak from a
16610
- * different header selection.
16611
- */
16612
- function baseHeader(theme) {
16613
- const { navLinkStyle: _navLinkStyle, navFontRole: _navFontRole, navTextSize: _navTextSize, dropdownStyle: _dropdownStyle, ctaVariant: _ctaVariant, navContainer: _navContainer, container: _container, shadow: _shadow, logo: _logo, logoText: _logoText, border: _border, ...header } = theme.header;
16614
- return {
16615
- ...header,
16616
- navContainer: {
16617
- type: "none",
16618
- tint: null,
16619
- opacity: .15
16620
- }
16621
- };
16622
- }
16623
16734
  const headerLayoutCatalog = defineExactCatalog()([
16624
16735
  {
16625
16736
  id: asHeaderLayoutId("classic"),
@@ -16660,43 +16771,37 @@ const headerStyleCatalog = defineExactCatalog()([
16660
16771
  id: asHeaderStyleId("minimal"),
16661
16772
  label: "Minimal",
16662
16773
  description: "Quiet surface with simple color-change navigation.",
16663
- supportedLayoutIds: supportedLayouts(["classic", "centered"]),
16664
- navTypography: "plain"
16774
+ supportedLayoutIds: supportedLayouts(["classic", "centered"])
16665
16775
  },
16666
16776
  {
16667
16777
  id: asHeaderStyleId("fine-line"),
16668
16778
  label: "Seamless",
16669
16779
  description: "Borderless surface with underline hover for a quieter page transition.",
16670
- supportedLayoutIds: supportedLayouts(["classic", "centered"]),
16671
- navTypography: "plain"
16780
+ supportedLayoutIds: supportedLayouts(["classic", "centered"])
16672
16781
  },
16673
16782
  {
16674
16783
  id: asHeaderStyleId("soft-bar"),
16675
16784
  label: "Soft Bar",
16676
16785
  description: "Soft tinted surface with gentle pill hover states.",
16677
- supportedLayoutIds: supportedLayouts(["classic", "centered"]),
16678
- navTypography: "plain"
16786
+ supportedLayoutIds: supportedLayouts(["classic", "centered"])
16679
16787
  },
16680
16788
  {
16681
16789
  id: asHeaderStyleId("pill-nav"),
16682
16790
  label: "Pill Nav",
16683
16791
  description: "A calm pill navigation rail over a neutral header.",
16684
- supportedLayoutIds: supportedLayouts(["classic", "centered"]),
16685
- navTypography: "plain"
16792
+ supportedLayoutIds: supportedLayouts(["classic", "centered"])
16686
16793
  },
16687
16794
  {
16688
16795
  id: asHeaderStyleId("brand-bar"),
16689
16796
  label: "Brand Bar",
16690
16797
  description: "Solid brand surface with high-contrast navigation.",
16691
- supportedLayoutIds: supportedLayouts(["classic", "centered"]),
16692
- navTypography: "brand-caps"
16798
+ supportedLayoutIds: supportedLayouts(["classic", "centered"])
16693
16799
  },
16694
16800
  {
16695
16801
  id: asHeaderStyleId("brand-underline"),
16696
16802
  label: "Brand Underline",
16697
16803
  description: "Brand surface with underline-grow navigation.",
16698
- supportedLayoutIds: supportedLayouts(["classic"]),
16699
- navTypography: "brand-caps"
16804
+ supportedLayoutIds: supportedLayouts(["classic"])
16700
16805
  },
16701
16806
  {
16702
16807
  id: asHeaderStyleId("brand-pill"),
@@ -16706,69 +16811,55 @@ const headerStyleCatalog = defineExactCatalog()([
16706
16811
  "classic",
16707
16812
  "centered",
16708
16813
  "floating"
16709
- ]),
16710
- navTypography: "brand-caps"
16814
+ ])
16711
16815
  },
16712
16816
  {
16713
16817
  id: asHeaderStyleId("brand-capsule"),
16714
16818
  label: "Brand Capsule",
16715
16819
  description: "Solid brand chrome designed as a floating capsule, with quiet-caps nav, a soft pill active state, and an inverted CTA pill.",
16716
- supportedLayoutIds: supportedLayouts(["classic"]),
16717
- navTypography: "quiet-caps"
16820
+ supportedLayoutIds: supportedLayouts(["classic"])
16718
16821
  },
16719
16822
  {
16720
16823
  id: asHeaderStyleId("serif-line"),
16721
16824
  label: "Serif Line",
16722
16825
  description: "Typographic logo treatment with understated underline navigation.",
16723
- supportedLayoutIds: supportedLayouts(["classic", "centered"]),
16724
- navTypography: "heading"
16826
+ supportedLayoutIds: supportedLayouts(["classic", "centered"])
16725
16827
  },
16726
16828
  {
16727
16829
  id: asHeaderStyleId("flat-tabs"),
16728
16830
  label: "Flat Tabs",
16729
16831
  description: "Flat block hover and active states with a Swiss utility feel.",
16730
- supportedLayoutIds: supportedLayouts(["classic", "centered"]),
16731
- navTypography: "quiet-caps"
16832
+ supportedLayoutIds: supportedLayouts(["classic", "centered"])
16732
16833
  },
16733
16834
  {
16734
16835
  id: asHeaderStyleId("glass"),
16735
16836
  label: "Glass",
16736
16837
  description: "Translucent chrome with frosted navigation backing.",
16737
- supportedLayoutIds: supportedLayouts(["floating"]),
16738
- navTypography: "plain"
16838
+ supportedLayoutIds: supportedLayouts(["floating"])
16739
16839
  },
16740
16840
  {
16741
16841
  id: asHeaderStyleId("minimal-surface"),
16742
16842
  label: "Clear Split",
16743
16843
  description: "Floating logo with transparent navigation and no shared shell.",
16744
- supportedLayoutIds: supportedLayouts(["floating"]),
16745
- navTypography: "plain",
16746
- openingForegroundTarget: "all",
16747
- scrollTreatment: "contrast-glass",
16748
- interactionPalette: clearSplitInteractionPalette
16844
+ supportedLayoutIds: supportedLayouts(["floating"])
16749
16845
  },
16750
16846
  {
16751
16847
  id: asHeaderStyleId("soft-shell"),
16752
16848
  label: "Soft Shell",
16753
16849
  description: "Floating rounded shell with soft pill navigation states.",
16754
- supportedLayoutIds: supportedLayouts(["floating"]),
16755
- navTypography: "plain"
16850
+ supportedLayoutIds: supportedLayouts(["floating"])
16756
16851
  },
16757
16852
  {
16758
16853
  id: asHeaderStyleId("split-glass"),
16759
16854
  label: "Split Glass",
16760
16855
  description: "Logo floats independently while the navigation sits in a glass rail.",
16761
- supportedLayoutIds: supportedLayouts(["floating"]),
16762
- navTypography: "quiet-caps",
16763
- openingForegroundTarget: "brand-only"
16856
+ supportedLayoutIds: supportedLayouts(["floating"])
16764
16857
  },
16765
16858
  {
16766
16859
  id: asHeaderStyleId("split-pill"),
16767
16860
  label: "Split Pill",
16768
16861
  description: "Logo floats independently with a soft pill-backed navigation rail.",
16769
- supportedLayoutIds: supportedLayouts(["floating"]),
16770
- navTypography: "heading",
16771
- openingForegroundTarget: "brand-only"
16862
+ supportedLayoutIds: supportedLayouts(["floating"])
16772
16863
  }
16773
16864
  ]);
16774
16865
  const headerLayoutsById = new Map(headerLayoutCatalog.map((layout) => [layout.id, layout]));
@@ -16955,6 +17046,551 @@ function resolveHeaderLayoutStyleSelection(selection) {
16955
17046
  };
16956
17047
  }
16957
17048
 
17049
+ //#endregion
17050
+ //#region ../theme-core/src/site-styles/headerInteractionPalette.ts
17051
+ const clearSplitInteractionPalette = defineLookOptionGroup({
17052
+ id: "interaction-palette",
17053
+ label: "Hover colour",
17054
+ defaultOptionId: "accent",
17055
+ options: [
17056
+ {
17057
+ id: "accent",
17058
+ label: "Accent"
17059
+ },
17060
+ {
17061
+ id: "primary",
17062
+ label: "Primary"
17063
+ },
17064
+ {
17065
+ id: "secondary",
17066
+ label: "Secondary"
17067
+ },
17068
+ {
17069
+ id: headerInteractionQuietPalette,
17070
+ label: "Text only"
17071
+ }
17072
+ ]
17073
+ });
17074
+ function resolveHeaderInteractionPaletteControl(group, preference) {
17075
+ if (!group) return { kind: "unavailable" };
17076
+ if (preference === "recommended") {
17077
+ const resolution$1 = resolveLookOption(group, undefined);
17078
+ return availableControlState(group, preference, resolution$1.option.id, null);
17079
+ }
17080
+ const resolution = resolveHeaderInteractionPalette(group, preference);
17081
+ return availableControlState(group, preference, resolution.option.id, resolution.kind === "repaired" ? {
17082
+ requested: preference,
17083
+ fallback: resolution.option.id,
17084
+ reason: resolution.reason
17085
+ } : null);
17086
+ }
17087
+ function availableControlState(group, preference, effectivePalette, repair$1) {
17088
+ return {
17089
+ kind: "available",
17090
+ preference,
17091
+ effectivePalette,
17092
+ options: [{
17093
+ value: "recommended",
17094
+ label: `Recommended (${defaultOptionLabel(group)})`
17095
+ }, ...group.options.map((option) => ({
17096
+ value: option.id,
17097
+ label: option.label
17098
+ }))],
17099
+ repair: repair$1
17100
+ };
17101
+ }
17102
+ function applyHeaderInteractionPalette(header, group, preference) {
17103
+ if (!group) return header;
17104
+ const palette = resolveHeaderInteractionPalette(group, preference).option.id;
17105
+ if (palette === headerInteractionQuietPalette) return header;
17106
+ if (!header.navLinkStyle) {
17107
+ throw new Error(`Header interaction palette "${group.id}" requires a nav link style.`);
17108
+ }
17109
+ const pair = headerInteractionPalettePair(palette);
17110
+ const pillEffect = {
17111
+ effectId: "background-on-hover",
17112
+ options: {
17113
+ colorToken: pair.backgroundToken,
17114
+ textColorToken: pair.foregroundToken
17115
+ }
17116
+ };
17117
+ return {
17118
+ ...header,
17119
+ navLinkStyle: {
17120
+ ...header.navLinkStyle,
17121
+ hoverColorToken: null,
17122
+ borderRadius: "full",
17123
+ effects: {
17124
+ ...header.navLinkStyle.effects,
17125
+ hover: [pillEffect],
17126
+ focus: [pillEffect]
17127
+ }
17128
+ }
17129
+ };
17130
+ }
17131
+ function resolveHeaderInteractionPalette(group, preference) {
17132
+ return resolveLookOption(group, preference === "recommended" ? undefined : preference);
17133
+ }
17134
+ function defaultOptionLabel(group) {
17135
+ return resolveLookOption(group, undefined).option.label;
17136
+ }
17137
+ /**
17138
+ * The four fields `applyHeaderInteractionPalette` writes, normalised so the
17139
+ * comparison below can be made against bytes that came back out of `jsonb`.
17140
+ *
17141
+ * `hoverColorToken` collapses absent to `null` because the palette writes an
17142
+ * explicit `null` where a pre-palette row simply has no key, and the two mean
17143
+ * the same thing.
17144
+ */
17145
+ function headerInteractionPaletteFields(header) {
17146
+ return {
17147
+ hoverColorToken: header.navLinkStyle?.hoverColorToken ?? null,
17148
+ borderRadius: header.navLinkStyle?.borderRadius,
17149
+ hover: header.navLinkStyle?.effects?.hover,
17150
+ focus: header.navLinkStyle?.effects?.focus
17151
+ };
17152
+ }
17153
+ /**
17154
+ * Key-sorted serialisation, because one side of the comparison is built here
17155
+ * and the other comes back out of `jsonb` in whatever order it was stored.
17156
+ * `JSON.stringify` alone reported two identical effect lists as different,
17157
+ * which is the same key-order trap the `logoText` derivation hit in Phase 4a.
17158
+ */
17159
+ function canonicalJson(value) {
17160
+ if (Array.isArray(value)) {
17161
+ return `[${value.map(canonicalJson).join(",")}]`;
17162
+ }
17163
+ if (typeof value === "object" && value !== null) {
17164
+ const entries = Object.entries(value).filter(([, member]) => member !== undefined).sort(([left], [right]) => left.localeCompare(right));
17165
+ return `{${entries.map(([key, member]) => `${JSON.stringify(key)}:${canonicalJson(member)}`).join(",")}}`;
17166
+ }
17167
+ return value === undefined ? "undefined" : JSON.stringify(value);
17168
+ }
17169
+ /**
17170
+ * The effect id the palette writes. A nav link carrying it is "palette-shaped":
17171
+ * its hover treatment is a filled pill rather than the plain colour change a
17172
+ * pre-palette recipe compiles.
17173
+ */
17174
+ const paletteEffectId = "background-on-hover";
17175
+ function carriesPaletteShapedEffect(header) {
17176
+ const effects = header.navLinkStyle?.effects;
17177
+ return [effects?.hover, effects?.focus].some((list) => (list ?? []).some((effect) => effect.effectId === paletteEffectId));
17178
+ }
17179
+ /**
17180
+ * Which option of `group` a persisted header's nav link style is the output of
17181
+ * (#1318 Phase 4c decision 2).
17182
+ *
17183
+ * This is a baseline comparison rather than a pattern match on effect bytes.
17184
+ * The palette overwrites its four fields unconditionally, so applying an option
17185
+ * to a header leaves that header unchanged exactly when the header is already
17186
+ * that option's output — which makes "which option produced these bytes" a
17187
+ * fixed-point search over the closed option list.
17188
+ *
17189
+ * Order is load-bearing twice over:
17190
+ *
17191
+ * - `'recommended'` is tried first, so a row compiled from the recommendation
17192
+ * derives the sentinel rather than the default option's own id. As with the
17193
+ * structure rule, an explicit choice that happens to equal the default
17194
+ * collapses to the sentinel; nothing in the bytes distinguishes the two.
17195
+ * - `'quiet'` is tried last, because it writes nothing and is therefore a fixed
17196
+ * point of every header. Reaching it means no pill option produced these
17197
+ * bytes — which is what "this row renders no pill" means, *provided the row
17198
+ * carries no pill*. `carriesPaletteShapedEffect` is that proviso: a
17199
+ * palette-shaped nav link that no option reproduces is `'unrecognised'`, not
17200
+ * `'quiet'`.
17201
+ */
17202
+ function classifyHeaderInteractionPaletteBytes(header, group) {
17203
+ if (!group) return { kind: "no-palette" };
17204
+ if (!header.navLinkStyle) return { kind: "no-palette" };
17205
+ const persisted = canonicalJson(headerInteractionPaletteFields(header));
17206
+ const match = orderedPaletteCandidates(group).find((candidate$1) => canonicalJson(headerInteractionPaletteFields(applyHeaderInteractionPalette(header, group, candidate$1))) === persisted);
17207
+ if (match === undefined) return { kind: "unrecognised" };
17208
+ if (match === headerInteractionQuietPalette && carriesPaletteShapedEffect(header)) {
17209
+ return { kind: "unrecognised" };
17210
+ }
17211
+ return {
17212
+ kind: "derived",
17213
+ preference: match
17214
+ };
17215
+ }
17216
+ /**
17217
+ * The preference `classifyHeaderInteractionPaletteBytes` derived, or `null`
17218
+ * when the bytes express none — either because the style offers no palette or
17219
+ * because they match no option it does offer. Callers that need to tell those
17220
+ * two apart use the classification directly.
17221
+ */
17222
+ function readHeaderInteractionPaletteFromBytes(header, group) {
17223
+ const classified = classifyHeaderInteractionPaletteBytes(header, group);
17224
+ return classified.kind === "derived" ? classified.preference : null;
17225
+ }
17226
+ function orderedPaletteCandidates(group) {
17227
+ const ids = group.options.map((option) => option.id);
17228
+ return [
17229
+ "recommended",
17230
+ ...ids.filter((id) => id !== headerInteractionQuietPalette),
17231
+ ...ids.filter((id) => id === headerInteractionQuietPalette)
17232
+ ];
17233
+ }
17234
+
17235
+ //#endregion
17236
+ //#region ../theme-core/src/site-styles/headerStyleDefaults.ts
17237
+ /**
17238
+ * A style either takes the default recipe or authors a complete one. This is
17239
+ * deliberately not a deep merge: the same "declare the variant or fall back"
17240
+ * rule cards use, so a half-authored recipe cannot exist.
17241
+ */
17242
+ function resolveHeaderStyleRecipe(siteStyle, styleId) {
17243
+ return siteStyle?.headerStyleRecipes?.[styleId] ?? defaultHeaderStyleRecipes[styleId];
17244
+ }
17245
+ /**
17246
+ * The default header appearance for every catalogue style id (#1318).
17247
+ *
17248
+ * This is the only id-keyed structure in the header appearance pipeline, and it
17249
+ * is data rather than dispatch — the same shape `cards/siteStyleDefaults.ts`
17250
+ * uses. A site style that wants a different header takes over an entry here
17251
+ * with its own recipe; it does not add a branch anywhere.
17252
+ *
17253
+ * `navWeight` (the bar) and `navLinkWeight` (the link text) are separate fields
17254
+ * and they disagree on five styles. That disagreement is real and pre-existing;
17255
+ * it is spelled out here rather than left implicit in a helper default. See the
17256
+ * note on `HeaderStyleRecipeCore.navLinkWeight`.
17257
+ */
17258
+ const defaultHeaderStyleRecipes = {
17259
+ minimal: {
17260
+ tone: "neutral",
17261
+ background: {
17262
+ kind: "token",
17263
+ token: "surface"
17264
+ },
17265
+ nav: {
17266
+ kind: "minimal",
17267
+ hoverColorToken: "primary"
17268
+ },
17269
+ navWeight: "medium",
17270
+ navLinkWeight: "medium",
17271
+ navTypography: "plain",
17272
+ border: "none",
17273
+ shadow: { kind: "absent" },
17274
+ frame: { kind: "none" },
17275
+ cta: { treatment: "default" }
17276
+ },
17277
+ "fine-line": {
17278
+ tone: "neutral",
17279
+ background: {
17280
+ kind: "token",
17281
+ token: "surface"
17282
+ },
17283
+ nav: {
17284
+ kind: "underline",
17285
+ hover: "underline"
17286
+ },
17287
+ navWeight: "medium",
17288
+ navLinkWeight: "medium",
17289
+ navTypography: "plain",
17290
+ border: "none",
17291
+ shadow: { kind: "absent" },
17292
+ frame: { kind: "none" },
17293
+ cta: { treatment: "outline" }
17294
+ },
17295
+ "soft-bar": {
17296
+ tone: "neutral",
17297
+ background: {
17298
+ kind: "token",
17299
+ token: "surfaceAlt"
17300
+ },
17301
+ nav: {
17302
+ kind: "pill",
17303
+ backing: "capsule",
17304
+ hoverColorToken: "primary",
17305
+ hoverAlpha: 8
17306
+ },
17307
+ navWeight: "medium",
17308
+ navLinkWeight: "medium",
17309
+ navTypography: "plain",
17310
+ border: "bottom",
17311
+ shadow: { kind: "absent" },
17312
+ frame: { kind: "none" },
17313
+ cta: { treatment: "default" }
17314
+ },
17315
+ "pill-nav": {
17316
+ tone: "neutral",
17317
+ background: {
17318
+ kind: "token",
17319
+ token: "surface"
17320
+ },
17321
+ nav: {
17322
+ kind: "pill",
17323
+ backing: "capsule",
17324
+ hoverColorToken: "primary",
17325
+ hoverAlpha: 10
17326
+ },
17327
+ navWeight: "medium",
17328
+ navLinkWeight: "medium",
17329
+ navTypography: "plain",
17330
+ border: "none",
17331
+ shadow: { kind: "absent" },
17332
+ frame: {
17333
+ kind: "nav-container",
17334
+ type: "pill",
17335
+ tint: "surfaceAlt",
17336
+ opacity: .7
17337
+ },
17338
+ cta: { treatment: "default" }
17339
+ },
17340
+ "brand-bar": {
17341
+ tone: "brand",
17342
+ background: {
17343
+ kind: "token",
17344
+ token: "primary"
17345
+ },
17346
+ nav: {
17347
+ kind: "minimal",
17348
+ hoverColorToken: "secondary"
17349
+ },
17350
+ navWeight: "semibold",
17351
+ navLinkWeight: "medium",
17352
+ navTypography: "brand-caps",
17353
+ border: "none",
17354
+ shadow: { kind: "absent" },
17355
+ frame: { kind: "none" },
17356
+ cta: { treatment: "inverted" }
17357
+ },
17358
+ "brand-underline": {
17359
+ tone: "brand",
17360
+ background: {
17361
+ kind: "token",
17362
+ token: "primary"
17363
+ },
17364
+ nav: {
17365
+ kind: "underline",
17366
+ hover: "both",
17367
+ hoverColorToken: "secondary",
17368
+ underlineColorToken: "secondary"
17369
+ },
17370
+ navWeight: "bold",
17371
+ navLinkWeight: "bold",
17372
+ navTypography: "brand-caps",
17373
+ border: "none",
17374
+ shadow: { kind: "absent" },
17375
+ frame: { kind: "none" },
17376
+ cta: {
17377
+ treatment: "inverted",
17378
+ gap: "compact"
17379
+ }
17380
+ },
17381
+ "brand-pill": {
17382
+ tone: "brand",
17383
+ background: {
17384
+ kind: "token",
17385
+ token: "primary"
17386
+ },
17387
+ nav: {
17388
+ kind: "pill",
17389
+ backing: "capsule",
17390
+ hoverColorToken: "primaryForeground",
17391
+ hoverAlpha: 14
17392
+ },
17393
+ navWeight: "semibold",
17394
+ navLinkWeight: "medium",
17395
+ navTypography: "brand-caps",
17396
+ border: "none",
17397
+ shadow: { kind: "absent" },
17398
+ frame: { kind: "none" },
17399
+ cta: { treatment: "inverted" },
17400
+ onFloating: {
17401
+ background: { kind: "transparent" },
17402
+ frame: {
17403
+ kind: "container",
17404
+ rounded: "2xl",
17405
+ padding: "sm",
17406
+ tint: "primary",
17407
+ opacity: .95
17408
+ }
17409
+ }
17410
+ },
17411
+ "brand-capsule": {
17412
+ tone: "brand",
17413
+ background: {
17414
+ kind: "token",
17415
+ token: "primary"
17416
+ },
17417
+ nav: {
17418
+ kind: "pill",
17419
+ backing: "capsule",
17420
+ hoverColorToken: "primaryForeground",
17421
+ hoverAlpha: 16
17422
+ },
17423
+ navWeight: "semibold",
17424
+ navLinkWeight: "medium",
17425
+ navTypography: "quiet-caps",
17426
+ border: "none",
17427
+ shadow: {
17428
+ kind: "fixed",
17429
+ elevation: "md"
17430
+ },
17431
+ frame: { kind: "none" },
17432
+ cta: { treatment: "inverted" }
17433
+ },
17434
+ "serif-line": {
17435
+ tone: "neutral",
17436
+ background: {
17437
+ kind: "token",
17438
+ token: "background"
17439
+ },
17440
+ nav: {
17441
+ kind: "underline",
17442
+ hover: "underline"
17443
+ },
17444
+ navWeight: "medium",
17445
+ navLinkWeight: "medium",
17446
+ navTypography: "heading",
17447
+ border: "none",
17448
+ shadow: { kind: "explicit-none" },
17449
+ frame: { kind: "none" },
17450
+ cta: {
17451
+ treatment: "outline",
17452
+ gap: "relaxed"
17453
+ },
17454
+ logo: {
17455
+ fontFamily: "heading",
17456
+ letterSpacing: "wide",
17457
+ gradient: false
17458
+ }
17459
+ },
17460
+ "flat-tabs": {
17461
+ tone: "neutral",
17462
+ background: {
17463
+ kind: "token",
17464
+ token: "surface"
17465
+ },
17466
+ nav: { kind: "flat-tab" },
17467
+ navWeight: "semibold",
17468
+ navLinkWeight: "semibold",
17469
+ navTypography: "quiet-caps",
17470
+ border: "bottom",
17471
+ shadow: { kind: "themed" },
17472
+ frame: { kind: "none" },
17473
+ cta: {
17474
+ treatment: "primary",
17475
+ gap: "compact"
17476
+ }
17477
+ },
17478
+ glass: {
17479
+ tone: "neutral",
17480
+ background: { kind: "transparent" },
17481
+ nav: {
17482
+ kind: "pill",
17483
+ backing: "frosted",
17484
+ hoverColorToken: "primary",
17485
+ hoverAlpha: 8
17486
+ },
17487
+ navWeight: "semibold",
17488
+ navLinkWeight: "medium",
17489
+ navTypography: "plain",
17490
+ border: "none",
17491
+ shadow: { kind: "absent" },
17492
+ frame: {
17493
+ kind: "container",
17494
+ rounded: "2xl",
17495
+ padding: "sm",
17496
+ tint: "surface",
17497
+ opacity: .4
17498
+ },
17499
+ cta: { treatment: "default" }
17500
+ },
17501
+ "minimal-surface": {
17502
+ tone: "neutral",
17503
+ background: { kind: "transparent" },
17504
+ nav: {
17505
+ kind: "minimal",
17506
+ hoverColorToken: "primary"
17507
+ },
17508
+ navWeight: "medium",
17509
+ navLinkWeight: "medium",
17510
+ navTypography: "plain",
17511
+ border: "none",
17512
+ shadow: { kind: "absent" },
17513
+ frame: { kind: "none" },
17514
+ cta: { treatment: "default" },
17515
+ navLinkExtras: {
17516
+ textSize: "lg",
17517
+ contrastTreatment: "soft-glow"
17518
+ },
17519
+ openingForeground: "all",
17520
+ scrollTreatment: "contrast-glass",
17521
+ interactionPalette: clearSplitInteractionPalette
17522
+ },
17523
+ "soft-shell": {
17524
+ tone: "neutral",
17525
+ background: { kind: "transparent" },
17526
+ nav: {
17527
+ kind: "pill",
17528
+ backing: "capsule",
17529
+ hoverColorToken: "primary",
17530
+ hoverAlpha: 8
17531
+ },
17532
+ navWeight: "medium",
17533
+ navLinkWeight: "medium",
17534
+ navTypography: "plain",
17535
+ border: "none",
17536
+ shadow: { kind: "absent" },
17537
+ frame: {
17538
+ kind: "container",
17539
+ rounded: "2xl",
17540
+ padding: "md",
17541
+ tint: "surfaceAlt",
17542
+ opacity: .95
17543
+ },
17544
+ cta: { treatment: "default" }
17545
+ },
17546
+ "split-glass": {
17547
+ tone: "neutral",
17548
+ background: { kind: "transparent" },
17549
+ nav: {
17550
+ kind: "pill",
17551
+ backing: "frosted",
17552
+ hoverColorToken: "primary",
17553
+ hoverAlpha: 8
17554
+ },
17555
+ navWeight: "semibold",
17556
+ navLinkWeight: "medium",
17557
+ navTypography: "quiet-caps",
17558
+ border: "none",
17559
+ shadow: { kind: "absent" },
17560
+ frame: {
17561
+ kind: "nav-container",
17562
+ type: "glass",
17563
+ tint: "surface",
17564
+ opacity: .4
17565
+ },
17566
+ cta: { treatment: "default" },
17567
+ openingForeground: "brand-only"
17568
+ },
17569
+ "split-pill": {
17570
+ tone: "neutral",
17571
+ background: { kind: "transparent" },
17572
+ nav: {
17573
+ kind: "pill",
17574
+ backing: "capsule",
17575
+ hoverColorToken: "primary",
17576
+ hoverAlpha: 10
17577
+ },
17578
+ navWeight: "medium",
17579
+ navLinkWeight: "medium",
17580
+ navTypography: "heading",
17581
+ border: "none",
17582
+ shadow: { kind: "absent" },
17583
+ frame: {
17584
+ kind: "nav-container",
17585
+ type: "pill",
17586
+ tint: "surface",
17587
+ opacity: .94
17588
+ },
17589
+ cta: { treatment: "default" },
17590
+ openingForeground: "brand-only"
17591
+ }
17592
+ };
17593
+
16958
17594
  //#endregion
16959
17595
  //#region ../theme-core/src/site-styles/headerNavStyles.ts
16960
17596
  function applyNavTypography(style, typography) {
@@ -17078,95 +17714,133 @@ function flatTabNavLinkStyle(colorToken, typography = "plain") {
17078
17714
  }
17079
17715
 
17080
17716
  //#endregion
17081
- //#region ../theme-core/src/site-styles/headerStyleCompiler.ts
17082
- function headerForLayout(theme, layout) {
17083
- return {
17084
- ...baseHeader(theme),
17085
- variant: layout.variant,
17086
- positioning: layout.defaultPositioning,
17087
- shrinkOnScroll: layout.shrinkOnScroll,
17088
- ctaGap: layout.id === asHeaderLayoutId("floating") ? "compact" : "default",
17089
- logoText: layout.id === asHeaderLayoutId("classic") ? {
17090
- mobileMaxWidth: "sm",
17091
- mobileWrapLines: 1,
17092
- hideOnShrink: false
17093
- } : undefined
17094
- };
17717
+ //#region ../theme-core/src/site-styles/headerStyleRecipe.ts
17718
+ function effectiveRecipe(layout, recipe) {
17719
+ return layout.id === asHeaderLayoutId("floating") && recipe.onFloating ? {
17720
+ ...recipe,
17721
+ ...recipe.onFloating
17722
+ } : recipe;
17095
17723
  }
17096
- function brandHeaderForeground() {
17097
- return {
17098
- textColor: "primaryForeground",
17099
- navColor: "primaryForeground"
17100
- };
17724
+ /**
17725
+ * One token drives the header text, the nav text and the nav link colour: they
17726
+ * are the same decision, and splitting them was how they could disagree.
17727
+ */
17728
+ function toneColorToken(tone) {
17729
+ switch (tone) {
17730
+ case "neutral": return "text";
17731
+ case "brand": return "primaryForeground";
17732
+ default: return assertNever$5(tone, "Unhandled header tone recipe");
17733
+ }
17101
17734
  }
17102
- function neutralDropdownStyle() {
17103
- return {
17104
- background: "surface",
17105
- textColor: "text",
17106
- borderColor: "border",
17107
- shadow: "md",
17108
- borderRadius: "md",
17109
- textTransform: "none",
17110
- letterSpacing: "normal"
17111
- };
17735
+ function toneDropdownStyle(tone) {
17736
+ switch (tone) {
17737
+ case "neutral": return {
17738
+ background: "surface",
17739
+ textColor: "text",
17740
+ borderColor: "border",
17741
+ shadow: "md",
17742
+ borderRadius: "md",
17743
+ textTransform: "none",
17744
+ letterSpacing: "normal"
17745
+ };
17746
+ case "brand": return {
17747
+ background: "primary-700",
17748
+ textColor: "primaryForeground",
17749
+ borderColor: null,
17750
+ shadow: "md",
17751
+ borderRadius: "md",
17752
+ hoverBackground: "primary-900",
17753
+ hoverTextColor: "secondary",
17754
+ textTransform: "uppercase",
17755
+ letterSpacing: "wider",
17756
+ fontWeight: "bold",
17757
+ textSize: "sm"
17758
+ };
17759
+ default: return assertNever$5(tone, "Unhandled header tone recipe");
17760
+ }
17112
17761
  }
17113
- function brandDropdownStyle() {
17114
- return {
17115
- background: "primary-700",
17116
- textColor: "primaryForeground",
17117
- borderColor: null,
17118
- shadow: "md",
17119
- borderRadius: "md",
17120
- hoverBackground: "primary-900",
17121
- hoverTextColor: "secondary",
17122
- textTransform: "uppercase",
17123
- letterSpacing: "wider",
17124
- fontWeight: "bold",
17125
- textSize: "sm"
17126
- };
17762
+ function recipeBackground(background) {
17763
+ switch (background.kind) {
17764
+ case "token": return {
17765
+ type: "color",
17766
+ color: background.token
17767
+ };
17768
+ case "transparent": return {
17769
+ type: "color",
17770
+ color: "transparent"
17771
+ };
17772
+ default: return assertNever$5(background, "Unhandled header background recipe");
17773
+ }
17127
17774
  }
17128
- function noHeaderBorder() {
17129
- return {
17130
- style: "solid",
17131
- width: "none",
17132
- position: "none"
17133
- };
17775
+ function recipeBorder(border$1) {
17776
+ switch (border$1) {
17777
+ case "none": return {
17778
+ style: "solid",
17779
+ width: "none",
17780
+ position: "none"
17781
+ };
17782
+ case "bottom": return {
17783
+ style: "solid",
17784
+ width: "thin",
17785
+ position: "bottom",
17786
+ colorToken: "border"
17787
+ };
17788
+ default: return assertNever$5(border$1, "Unhandled header border recipe");
17789
+ }
17134
17790
  }
17135
- function bottomHeaderBorder() {
17136
- return {
17137
- style: "solid",
17138
- width: "thin",
17139
- position: "bottom",
17140
- colorToken: "border"
17141
- };
17791
+ function recipeNavStyle(nav) {
17792
+ switch (nav.kind) {
17793
+ case "minimal": return "minimal";
17794
+ case "underline": return "underline-grow";
17795
+ case "pill": return nav.backing;
17796
+ case "flat-tab": return "solid";
17797
+ default: return assertNever$5(nav, "Unhandled header nav recipe");
17798
+ }
17142
17799
  }
17143
- function colorHeaderBackground(color) {
17144
- return {
17145
- type: "color",
17146
- color
17800
+ function underlineHoverEffects(nav) {
17801
+ const underline = {
17802
+ effectId: "nav-underline",
17803
+ options: {
17804
+ style: "grow",
17805
+ ...nav.underlineColorToken ? { colorToken: nav.underlineColorToken } : {}
17806
+ }
17147
17807
  };
17808
+ switch (nav.hover) {
17809
+ case "underline": return [underline];
17810
+ case "colour": return [{
17811
+ effectId: "text-color-change",
17812
+ options: { hoverColorToken: nav.hoverColorToken }
17813
+ }];
17814
+ case "both": return [{
17815
+ effectId: "text-color-change",
17816
+ options: { hoverColorToken: nav.hoverColorToken }
17817
+ }, underline];
17818
+ default: return assertNever$5(nav, "Unhandled underline nav hover affordance");
17819
+ }
17148
17820
  }
17149
- function neutralHeaderBase() {
17821
+ function underlineNavLink(nav, colorToken, typography) {
17822
+ const base = underlineNavLinkStyle(colorToken, nav.underlineColorToken, typography);
17150
17823
  return {
17151
- textColor: "text",
17152
- navColor: "text",
17153
- dropdownStyle: neutralDropdownStyle()
17824
+ ...base,
17825
+ ...nav.hover === "underline" ? {} : { hoverColorToken: nav.hoverColorToken },
17826
+ effects: {
17827
+ ...base.effects,
17828
+ hover: underlineHoverEffects(nav)
17829
+ }
17154
17830
  };
17155
17831
  }
17156
- function transparentHeaderBackground() {
17157
- return colorHeaderBackground("transparent");
17158
- }
17159
- function floatingContainer(input) {
17160
- return {
17161
- rounded: input.rounded,
17162
- border: true,
17163
- shadow: true,
17164
- padding: input.padding,
17165
- tint: input.tint,
17166
- opacity: input.opacity
17167
- };
17832
+ function recipeNavLinkStyle(recipe) {
17833
+ const colorToken = toneColorToken(recipe.tone);
17834
+ const nav = recipe.nav;
17835
+ switch (nav.kind) {
17836
+ case "minimal": return minimalNavLinkStyle(colorToken, nav.hoverColorToken, recipe.navTypography);
17837
+ case "underline": return underlineNavLink(nav, colorToken, recipe.navTypography);
17838
+ case "pill": return pillNavLinkStyle(colorToken, nav.hoverColorToken, nav.hoverAlpha, recipe.navTypography);
17839
+ case "flat-tab": return flatTabNavLinkStyle(colorToken, recipe.navTypography);
17840
+ default: return assertNever$5(nav, "Unhandled header nav recipe");
17841
+ }
17168
17842
  }
17169
- function themedHeaderShadow(theme) {
17843
+ function themedShadow(theme) {
17170
17844
  switch (theme.shadow.elevation) {
17171
17845
  case "none": return undefined;
17172
17846
  case "low": return { elevation: "sm" };
@@ -17175,248 +17849,162 @@ function themedHeaderShadow(theme) {
17175
17849
  default: return assertNever$5(theme.shadow.elevation, "Unhandled theme shadow elevation");
17176
17850
  }
17177
17851
  }
17178
- function brandSurfaceBase(theme, header) {
17179
- const ctaVariant = resolveHeaderCtaVariant(theme, "inverted");
17852
+ function recipeShadow(shadow$1, theme) {
17853
+ switch (shadow$1.kind) {
17854
+ case "absent": return {};
17855
+ case "explicit-none": return { shadow: { elevation: "none" } };
17856
+ case "fixed": return { shadow: { elevation: shadow$1.elevation } };
17857
+ case "themed": {
17858
+ const resolved = themedShadow(theme);
17859
+ return resolved ? { shadow: resolved } : {};
17860
+ }
17861
+ default: return assertNever$5(shadow$1, "Unhandled header shadow recipe");
17862
+ }
17863
+ }
17864
+ /**
17865
+ * `{kind: 'none'}` writes nothing: the base header already carries the
17866
+ * `{type: 'none', tint: null, opacity: 0.15}` nav container default, and
17867
+ * omitting the key would be a different JSON.
17868
+ */
17869
+ function recipeFrame(frame) {
17870
+ switch (frame.kind) {
17871
+ case "none": return {};
17872
+ case "container": return { container: {
17873
+ rounded: frame.rounded,
17874
+ border: true,
17875
+ shadow: true,
17876
+ padding: frame.padding,
17877
+ tint: frame.tint,
17878
+ opacity: frame.opacity
17879
+ } };
17880
+ case "nav-container": return { navContainer: {
17881
+ type: frame.type,
17882
+ tint: frame.tint,
17883
+ opacity: frame.opacity
17884
+ } };
17885
+ default: return assertNever$5(frame, "Unhandled header frame recipe");
17886
+ }
17887
+ }
17888
+ /**
17889
+ * `ctaVariant` is spread conditionally everywhere, which is byte-safe for the
17890
+ * nine `'default'` styles only because `resolveHeaderCtaVariant(theme,
17891
+ * 'default')` returns `undefined`. The compiler used to mix a conditional
17892
+ * spread with three direct assignments; the difference was invisible in
17893
+ * persisted JSON and visible to an in-memory `toEqual`, so it is normalised
17894
+ * here (ledger D1).
17895
+ */
17896
+ function recipeCta(cta, theme) {
17897
+ const variant = resolveHeaderCtaVariant(theme, cta.treatment);
17180
17898
  return {
17181
- ...header,
17182
- ...brandHeaderForeground(),
17183
- background: colorHeaderBackground("primary"),
17184
- dropdownStyle: brandDropdownStyle(),
17185
- border: noHeaderBorder(),
17186
- ctaTreatment: "inverted",
17187
- ...ctaVariant ? { ctaVariant } : {}
17899
+ ctaTreatment: cta.treatment,
17900
+ ...variant ? { ctaVariant: variant } : {},
17901
+ ...cta.gap ? { ctaGap: cta.gap } : {}
17188
17902
  };
17189
17903
  }
17190
- function floatingSurfaceBase(header) {
17904
+ /**
17905
+ * Layout-derived structure. The only part of the header a recipe cannot reach.
17906
+ *
17907
+ * It used to start from `baseHeader(theme)` — the previous header with the
17908
+ * style-owned keys stripped — which is what let a value hand-edited once
17909
+ * survive every later recompile. Phase 4e deleted that (#1318): the layout
17910
+ * writes its own fields and nothing else arrives from the theme's header.
17911
+ *
17912
+ * Three fields moved here as a result, each with a named producer rather than
17913
+ * an inherited value:
17914
+ *
17915
+ * - `maxWidth` is the constant `'container'` (D16). It is layout-owned with one
17916
+ * value; `headerSchema` makes it required on the *output* type, so a
17917
+ * `.default()` at the parse boundary would not do — that would be a hidden
17918
+ * authority, which is the thing this slice removes.
17919
+ * - `navContainer` is `baseHeader`'s old default, which a `frame: {kind:'none'}`
17920
+ * recipe still relies on being present rather than omitted.
17921
+ * - `scrollTreatment` and `interactionPalette` are deliberately **absent**.
17922
+ * Neither is compiled here: `scrollTreatment` is written at render time by
17923
+ * `applyOpeningForeground` from the page's own context, and the interaction
17924
+ * palette's marker field is written by `applyHeaderPreferences` from the
17925
+ * record. Both used to reach the output only by surviving `baseHeader`.
17926
+ */
17927
+ function headerForLayout(layout) {
17191
17928
  return {
17192
- ...header,
17193
- background: transparentHeaderBackground(),
17194
- border: noHeaderBorder()
17195
- };
17196
- }
17197
- function applyBasicHeaderStyle(id, theme, header, style) {
17198
- switch (id) {
17199
- case "minimal": return {
17200
- ...header,
17201
- ...neutralHeaderBase(),
17202
- background: colorHeaderBackground("surface"),
17203
- navStyle: "minimal",
17204
- navWeight: "medium",
17205
- navLinkStyle: minimalNavLinkStyle("text", "primary", style.navTypography),
17206
- border: noHeaderBorder(),
17207
- ctaTreatment: "default"
17208
- };
17209
- case "fine-line": return {
17210
- ...header,
17211
- ...neutralHeaderBase(),
17212
- background: colorHeaderBackground("surface"),
17213
- navStyle: "underline-grow",
17214
- navWeight: "medium",
17215
- navLinkStyle: underlineNavLinkStyle("text", undefined, style.navTypography),
17216
- border: noHeaderBorder(),
17217
- ctaTreatment: "outline",
17218
- ctaVariant: resolveHeaderCtaVariant(theme, "outline")
17219
- };
17220
- case "soft-bar": return {
17221
- ...header,
17222
- ...neutralHeaderBase(),
17223
- background: colorHeaderBackground("surfaceAlt"),
17224
- navStyle: "capsule",
17225
- navWeight: "medium",
17226
- navLinkStyle: pillNavLinkStyle("text", "primary", 8, style.navTypography),
17227
- border: bottomHeaderBorder(),
17228
- ctaTreatment: "default"
17229
- };
17230
- case "pill-nav": return {
17231
- ...header,
17232
- ...neutralHeaderBase(),
17233
- background: colorHeaderBackground("surface"),
17234
- navStyle: "capsule",
17235
- navWeight: "medium",
17236
- navLinkStyle: pillNavLinkStyle("text", "primary", 10, style.navTypography),
17237
- navContainer: {
17238
- type: "pill",
17239
- tint: "surfaceAlt",
17240
- opacity: .7
17241
- },
17242
- border: noHeaderBorder(),
17243
- ctaTreatment: "default"
17244
- };
17245
- default: return assertNever$5(id, "Unhandled basic header style id");
17246
- }
17247
- }
17248
- function applyBrandHeaderStyle(id, theme, header, style) {
17249
- switch (id) {
17250
- case "brand-bar": return {
17251
- ...brandSurfaceBase(theme, header),
17252
- navStyle: "minimal",
17253
- navWeight: "semibold",
17254
- navLinkStyle: minimalNavLinkStyle("primaryForeground", "secondary", style.navTypography)
17255
- };
17256
- case "brand-underline": return {
17257
- ...brandSurfaceBase(theme, header),
17258
- navStyle: "underline-grow",
17259
- navWeight: "bold",
17260
- navLinkStyle: underlineNavLinkStyle("primaryForeground", "secondary", style.navTypography)
17261
- };
17262
- case "brand-pill": return {
17263
- ...brandSurfaceBase(theme, header),
17264
- ...header.variant === "floating" ? {
17265
- background: transparentHeaderBackground(),
17266
- container: floatingContainer({
17267
- rounded: "2xl",
17268
- padding: "sm",
17269
- tint: "primary",
17270
- opacity: .95
17271
- })
17272
- } : {},
17273
- navStyle: "capsule",
17274
- navWeight: "semibold",
17275
- navLinkStyle: pillNavLinkStyle("primaryForeground", "primaryForeground", 14, style.navTypography)
17276
- };
17277
- case "brand-capsule": return {
17278
- ...brandSurfaceBase(theme, header),
17279
- navStyle: "capsule",
17280
- navWeight: "semibold",
17281
- navLinkStyle: pillNavLinkStyle("primaryForeground", "primaryForeground", 16, style.navTypography),
17282
- shadow: { elevation: "md" }
17283
- };
17284
- default: return assertNever$5(id, "Unhandled brand header style id");
17285
- }
17286
- }
17287
- function applyEditorialHeaderStyle(id, theme, header, style) {
17288
- switch (id) {
17289
- case "serif-line": return {
17290
- ...header,
17291
- ...neutralHeaderBase(),
17292
- background: colorHeaderBackground("background"),
17293
- navStyle: "underline-grow",
17294
- navWeight: "medium",
17295
- navLinkStyle: underlineNavLinkStyle("text", undefined, style.navTypography),
17296
- border: noHeaderBorder(),
17297
- shadow: { elevation: "none" },
17298
- logo: {
17299
- fontFamily: "heading",
17300
- letterSpacing: "wide",
17301
- gradient: false
17302
- },
17303
- ctaTreatment: "outline",
17304
- ctaVariant: resolveHeaderCtaVariant(theme, "outline"),
17305
- ctaGap: "relaxed"
17306
- };
17307
- case "flat-tabs": return {
17308
- ...header,
17309
- ...neutralHeaderBase(),
17310
- background: colorHeaderBackground("surface"),
17311
- navStyle: "solid",
17312
- navWeight: "semibold",
17313
- navLinkStyle: flatTabNavLinkStyle("text", style.navTypography),
17314
- border: bottomHeaderBorder(),
17315
- shadow: themedHeaderShadow(theme),
17316
- ctaTreatment: "primary",
17317
- ctaVariant: resolveHeaderCtaVariant(theme, "primary"),
17318
- ctaGap: "compact"
17319
- };
17320
- default: return assertNever$5(id, "Unhandled editorial header style id");
17321
- }
17322
- }
17323
- function applyFloatingHeaderStyle(id, header, style) {
17324
- switch (id) {
17325
- case "glass": return {
17326
- ...floatingSurfaceBase(header),
17327
- ...neutralHeaderBase(),
17328
- navStyle: "frosted",
17329
- navWeight: "semibold",
17330
- navLinkStyle: pillNavLinkStyle("text", "primary", 8, style.navTypography),
17331
- container: floatingContainer({
17332
- rounded: "2xl",
17333
- padding: "sm",
17334
- tint: "surface",
17335
- opacity: .4
17336
- }),
17337
- ctaTreatment: "default"
17338
- };
17339
- case "minimal-surface": return {
17340
- ...floatingSurfaceBase(header),
17341
- ...neutralHeaderBase(),
17342
- navStyle: "minimal",
17343
- navWeight: "medium",
17344
- navLinkStyle: {
17345
- ...minimalNavLinkStyle("text", "primary", style.navTypography),
17346
- textSize: "lg",
17347
- contrastTreatment: "soft-glow"
17348
- },
17349
- ctaTreatment: "default"
17350
- };
17351
- case "soft-shell": return {
17352
- ...floatingSurfaceBase(header),
17353
- ...neutralHeaderBase(),
17354
- navStyle: "capsule",
17355
- navWeight: "medium",
17356
- navLinkStyle: pillNavLinkStyle("text", "primary", 8, style.navTypography),
17357
- container: floatingContainer({
17358
- rounded: "2xl",
17359
- padding: "md",
17360
- tint: "surfaceAlt",
17361
- opacity: .95
17362
- }),
17363
- ctaTreatment: "default"
17364
- };
17365
- case "split-glass": return {
17366
- ...floatingSurfaceBase(header),
17367
- ...neutralHeaderBase(),
17368
- navStyle: "frosted",
17369
- navWeight: "semibold",
17370
- navLinkStyle: pillNavLinkStyle("text", "primary", 8, style.navTypography),
17371
- navContainer: {
17372
- type: "glass",
17373
- tint: "surface",
17374
- opacity: .4
17375
- },
17376
- ctaTreatment: "default"
17377
- };
17378
- case "split-pill": return {
17379
- ...floatingSurfaceBase(header),
17380
- ...neutralHeaderBase(),
17381
- navStyle: "capsule",
17382
- navWeight: "medium",
17383
- navLinkStyle: pillNavLinkStyle("text", "primary", 10, style.navTypography),
17384
- navContainer: {
17385
- type: "pill",
17386
- tint: "surface",
17387
- opacity: .94
17388
- },
17389
- ctaTreatment: "default"
17390
- };
17391
- default: return assertNever$5(id, "Unhandled floating header style id");
17392
- }
17393
- }
17394
- const headerStyleCompilers = {
17395
- minimal: (theme, header, style) => applyBasicHeaderStyle("minimal", theme, header, style),
17396
- "fine-line": (theme, header, style) => applyBasicHeaderStyle("fine-line", theme, header, style),
17397
- "soft-bar": (theme, header, style) => applyBasicHeaderStyle("soft-bar", theme, header, style),
17398
- "pill-nav": (theme, header, style) => applyBasicHeaderStyle("pill-nav", theme, header, style),
17399
- "brand-bar": (theme, header, style) => applyBrandHeaderStyle("brand-bar", theme, header, style),
17400
- "brand-underline": (theme, header, style) => applyBrandHeaderStyle("brand-underline", theme, header, style),
17401
- "brand-pill": (theme, header, style) => applyBrandHeaderStyle("brand-pill", theme, header, style),
17402
- "brand-capsule": (theme, header, style) => applyBrandHeaderStyle("brand-capsule", theme, header, style),
17403
- "serif-line": (theme, header, style) => applyEditorialHeaderStyle("serif-line", theme, header, style),
17404
- "flat-tabs": (theme, header, style) => applyEditorialHeaderStyle("flat-tabs", theme, header, style),
17405
- glass: (_theme, header, style) => applyFloatingHeaderStyle("glass", header, style),
17406
- "minimal-surface": (_theme, header, style) => applyFloatingHeaderStyle("minimal-surface", header, style),
17407
- "soft-shell": (_theme, header, style) => applyFloatingHeaderStyle("soft-shell", header, style),
17408
- "split-glass": (_theme, header, style) => applyFloatingHeaderStyle("split-glass", header, style),
17409
- "split-pill": (_theme, header, style) => applyFloatingHeaderStyle("split-pill", header, style)
17410
- };
17411
- function applyHeaderStyle(theme, header, style) {
17412
- return headerStyleCompilers[headerStyleIdValue(style.id)](theme, header, style);
17929
+ maxWidth: "container",
17930
+ navContainer: {
17931
+ type: "none",
17932
+ tint: null,
17933
+ opacity: .15
17934
+ },
17935
+ variant: layout.variant,
17936
+ positioning: layout.defaultPositioning,
17937
+ shrinkOnScroll: layout.shrinkOnScroll,
17938
+ ctaGap: layout.id === asHeaderLayoutId("floating") ? "compact" : "default",
17939
+ logoText: layout.id === asHeaderLayoutId("classic") ? {
17940
+ mobileMaxWidth: "sm",
17941
+ mobileWrapLines: 1,
17942
+ hideOnShrink: false
17943
+ } : undefined
17944
+ };
17413
17945
  }
17414
- function headerStyleIdValue(id) {
17415
- return id;
17946
+ /**
17947
+ * The single header interpreter. Pure and total: same `(theme, layout, recipe)`
17948
+ * in, same object out, no throws of its own.
17949
+ *
17950
+ * It **constructs** a `ThemeHeader` rather than starting from one (#1318 Phase
17951
+ * 4e). `theme` is read for the palette-dependent facets only — the themed
17952
+ * shadow and the CTA variant — never for `theme.header`, so no value can
17953
+ * survive a recompile by accident. Every required field of `headerSchema` is
17954
+ * produced by a named branch here or in `headerForLayout`; the type is the
17955
+ * check.
17956
+ */
17957
+ function compileHeaderRecipe(theme, layout, recipe) {
17958
+ const effective = effectiveRecipe(layout, recipe);
17959
+ const navLinkStyle = recipeNavLinkStyle(effective);
17960
+ return {
17961
+ ...headerForLayout(layout),
17962
+ textColor: toneColorToken(effective.tone),
17963
+ navColor: toneColorToken(effective.tone),
17964
+ dropdownStyle: toneDropdownStyle(effective.tone),
17965
+ background: recipeBackground(effective.background),
17966
+ border: recipeBorder(effective.border),
17967
+ navStyle: recipeNavStyle(effective.nav),
17968
+ navWeight: effective.navWeight,
17969
+ navLinkStyle: {
17970
+ ...navLinkStyle,
17971
+ fontWeight: effective.navLinkWeight,
17972
+ ...effective.navLinkExtras ?? {}
17973
+ },
17974
+ ...recipeShadow(effective.shadow, theme),
17975
+ ...recipeFrame(effective.frame),
17976
+ ...effective.logo ? { logo: effective.logo } : {},
17977
+ ...recipeCta(effective.cta, theme)
17978
+ };
17416
17979
  }
17417
- function compileHeaderLayoutStyleSelection(theme, resolution) {
17418
- const header = applyHeaderStyle(theme, headerForLayout(theme, resolution.layout), resolution.style);
17419
- return applyHeaderInteractionPalette(header, resolution.style.interactionPalette, theme.header.interactionPalette ?? "recommended");
17980
+
17981
+ //#endregion
17982
+ //#region ../theme-core/src/site-styles/headerStyleCompiler.ts
17983
+ /**
17984
+ * The header compilation pipeline: pick the recipe, interpret it, then apply
17985
+ * the user's interaction-palette preference.
17986
+ *
17987
+ * Appearance decisions live in `headerStyleDefaults.ts` (data), optionally
17988
+ * taken over per site style, and `headerStyleRecipe.ts` (one pure interpreter).
17989
+ * Nothing here switches on a header style id.
17990
+ *
17991
+ * `siteStyle` is required rather than optional: a caller that has no resolved
17992
+ * site style has to say so, because silently falling back to the catalogue
17993
+ * default is exactly the ownership leak this slice closes. Pass `null` when
17994
+ * there genuinely is no site style (initial site provisioning).
17995
+ *
17996
+ * `interactionPalette` is a parameter rather than a read off
17997
+ * `theme.header.interactionPalette` (#1318 Phase 4c). The marker field on the
17998
+ * header is where the preference is *persisted*, but taking it from there made
17999
+ * the palette the one preference the compiler could not be told about — which
18000
+ * is invisible while the marker always equals what was compiled and wrong the
18001
+ * moment a backfill supplies a different one. The caller that owns the
18002
+ * preference record now says which palette to compile.
18003
+ */
18004
+ function compileHeaderLayoutStyleSelection(theme, resolution, siteStyle, interactionPalette) {
18005
+ const recipe = resolveHeaderStyleRecipe(siteStyle, resolution.style.id);
18006
+ const header = compileHeaderRecipe(theme, resolution.layout, recipe);
18007
+ return applyHeaderInteractionPalette(header, recipe.interactionPalette, interactionPalette);
17420
18008
  }
17421
18009
 
17422
18010
  //#endregion
@@ -18228,7 +18816,6 @@ const modernMinimalTheme = {
18228
18816
  positioning: "static",
18229
18817
  shrinkOnScroll: false,
18230
18818
  maxWidth: "container",
18231
- logoOverride: null,
18232
18819
  background: {
18233
18820
  type: "color",
18234
18821
  color: "surface"
@@ -19933,10 +20520,22 @@ function defaultPaletteForSiteStyle(siteStyle) {
19933
20520
  return defaultCuratedChoice(siteStyle.paletteRecommendations);
19934
20521
  }
19935
20522
  function defaultHeaderSelectionForSiteStyle(siteStyle) {
20523
+ return defaultHeaderCurationForSiteStyle(siteStyle).selection;
20524
+ }
20525
+ /**
20526
+ * The curated default selection together with the style choices that belong to
20527
+ * it. Carrying the styles alongside the ids is what lets a caller fall back to
20528
+ * the default layout without a second, nullable lookup that can never actually
20529
+ * miss — the source of an unreachable throw before #1318 Phase 4a.
20530
+ */
20531
+ function defaultHeaderCurationForSiteStyle(siteStyle) {
19936
20532
  const [{ layout, styles }] = siteStyle.headerCuration;
19937
20533
  return {
19938
- layoutId: layout.id,
19939
- styleId: styles[0].id
20534
+ selection: {
20535
+ layoutId: layout.id,
20536
+ styleId: styles[0].id
20537
+ },
20538
+ styles
19940
20539
  };
19941
20540
  }
19942
20541
  function headerLayoutChoicesForSiteStyle(siteStyle) {
@@ -21085,6 +21684,19 @@ const curatedSiteStyles = [
21085
21684
  })
21086
21685
  ];
21087
21686
  const curatedSiteStylesById = new Map(curatedSiteStyles.map((style) => [style.id, style]));
21687
+ /**
21688
+ * The header appearance a persisted theme actually renders: the catalogue
21689
+ * recipe unless the theme's own site style has taken that id over (#1318).
21690
+ *
21691
+ * Consumers that hold a theme and a selected style id — the public frontend and
21692
+ * the chrome customiser — go through here rather than reading the style
21693
+ * definition, so a site-style override is visible to every reader rather than
21694
+ * only to the compiler.
21695
+ */
21696
+ function headerStyleRecipeForTheme(theme, styleId) {
21697
+ const siteStyle = theme?.siteStyleId ? getCuratedSiteStyle(theme.siteStyleId) : null;
21698
+ return resolveHeaderStyleRecipe(siteStyle, styleId);
21699
+ }
21088
21700
  function getCuratedSiteStyle(id) {
21089
21701
  return curatedSiteStylesById.get(id) ?? null;
21090
21702
  }
@@ -22478,6 +23090,878 @@ function reinterpretForSiteStyle(input) {
22478
23090
  };
22479
23091
  }
22480
23092
 
23093
+ //#endregion
23094
+ //#region ../theme-core/src/site-styles/siteChromeBackgrounds.ts
23095
+ const siteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceColorTokenNames);
23096
+ const quickSiteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceQuickColorTokenNames);
23097
+ const advancedSiteChromeBackgroundColorTokens = siteChromeBackgroundColorTokens.filter((token) => !quickSiteChromeBackgroundColorTokens.includes(token));
23098
+ const defaultSiteChromeBackgroundOverride = { kind: "default" };
23099
+ function defaultSiteChromeBackgroundPreferences() {
23100
+ return {
23101
+ header: defaultSiteChromeBackgroundOverride,
23102
+ footer: defaultSiteChromeBackgroundOverride
23103
+ };
23104
+ }
23105
+ function parseSiteChromeBackgroundColorToken(value) {
23106
+ const token = parseBackgroundAppearanceColorToken(value);
23107
+ return token ? token : null;
23108
+ }
23109
+ function siteChromeBackgroundPreferencesFromTheme(theme) {
23110
+ if (!theme) return defaultSiteChromeBackgroundPreferences();
23111
+ return {
23112
+ header: theme.header ? siteChromeBackgroundOverrideFromHeader(theme.header) : defaultSiteChromeBackgroundOverride,
23113
+ footer: theme.footer ? siteChromeBackgroundOverrideFromFooter(theme.footer) : defaultSiteChromeBackgroundOverride
23114
+ };
23115
+ }
23116
+ function applySiteChromeBackgroundPreferences(theme, preferences) {
23117
+ return {
23118
+ ...theme,
23119
+ header: applyHeaderBackgroundOverride(theme, theme.header, preferences.header),
23120
+ footer: applyFooterBackgroundOverride(theme, theme.footer, preferences.footer)
23121
+ };
23122
+ }
23123
+ function canCustomizeSiteChromeBackground(theme, surface$1) {
23124
+ if (surface$1 === "footer") return true;
23125
+ const header = theme?.header;
23126
+ if (!header) return false;
23127
+ const navContainerType = header.navContainer?.type ?? "none";
23128
+ return header.variant !== "floating" && navContainerType !== "glass" && navContainerType !== "pill";
23129
+ }
23130
+ function siteChromeBackgroundOverrideLabel(override) {
23131
+ switch (override.kind) {
23132
+ case "default": return "Default";
23133
+ case "color": return formatSiteChromeBackgroundTokenLabel(override.token);
23134
+ }
23135
+ }
23136
+ function formatSiteChromeBackgroundTokenLabel(token) {
23137
+ return String(token).replace(/-/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
23138
+ }
23139
+ function siteChromeBackgroundOverrideEquals(left, right) {
23140
+ return left.kind === right.kind && (left.kind === "default" || right.kind === "color" && left.token === right.token);
23141
+ }
23142
+ function cycleSiteChromeBackgroundOverride(current, direction) {
23143
+ const fallback = direction === "next" ? quickSiteChromeBackgroundColorTokens[0] : quickSiteChromeBackgroundColorTokens[quickSiteChromeBackgroundColorTokens.length - 1];
23144
+ if (!fallback) return defaultSiteChromeBackgroundOverride;
23145
+ if (current.kind === "default") {
23146
+ return {
23147
+ kind: "color",
23148
+ token: fallback
23149
+ };
23150
+ }
23151
+ const currentIndex = quickSiteChromeBackgroundColorTokens.indexOf(current.token);
23152
+ if (currentIndex === -1) {
23153
+ return {
23154
+ kind: "color",
23155
+ token: fallback
23156
+ };
23157
+ }
23158
+ const nextIndex = direction === "next" ? (currentIndex + 1) % quickSiteChromeBackgroundColorTokens.length : currentIndex === 0 ? quickSiteChromeBackgroundColorTokens.length - 1 : currentIndex - 1;
23159
+ const token = requiredQuickSiteChromeBackgroundColorToken(nextIndex);
23160
+ return {
23161
+ kind: "color",
23162
+ token
23163
+ };
23164
+ }
23165
+ /**
23166
+ * Reads the header background override back out of persisted bytes. Exported
23167
+ * for `readHeaderPreferences`, which needs the header half on its own.
23168
+ */
23169
+ function siteChromeBackgroundOverrideFromHeader(header) {
23170
+ return siteChromeBackgroundOverrideFromBackground(header.background);
23171
+ }
23172
+ function siteChromeBackgroundOverrideFromFooter(footer) {
23173
+ return siteChromeBackgroundOverrideFromBackground(footer.background);
23174
+ }
23175
+ function siteChromeBackgroundOverrideFromBackground(background) {
23176
+ if (background?.contentOwner !== siteChromeBackgroundOverrideOwner || background.type !== "color") {
23177
+ return defaultSiteChromeBackgroundOverride;
23178
+ }
23179
+ const token = parseSiteChromeBackgroundColorToken(background.color);
23180
+ return token ? {
23181
+ kind: "color",
23182
+ token
23183
+ } : defaultSiteChromeBackgroundOverride;
23184
+ }
23185
+ /**
23186
+ * Header-only background applier. Exported so the header preference pipeline
23187
+ * can apply the header override without going through
23188
+ * `applySiteChromeBackgroundPreferences`, which also writes the footer and
23189
+ * would clobber footer intent (#1318 Correction 20).
23190
+ */
23191
+ function applyHeaderBackgroundOverride(theme, header, override) {
23192
+ if (override.kind === "default") return header;
23193
+ const background = siteChromeColorBackground(override.token);
23194
+ const foreground = resolveChromeForegroundToken(theme, background);
23195
+ return {
23196
+ ...header,
23197
+ background: {
23198
+ ...background,
23199
+ textColor: foreground
23200
+ },
23201
+ textColor: foreground,
23202
+ navColor: foreground,
23203
+ ...header.navLinkStyle ? { navLinkStyle: {
23204
+ ...header.navLinkStyle,
23205
+ colorToken: foreground
23206
+ } } : {}
23207
+ };
23208
+ }
23209
+ /**
23210
+ * Footer-only background applier, the counterpart of
23211
+ * `applyHeaderBackgroundOverride`. Exported so a caller that owns one surface
23212
+ * can write that surface rather than passing a no-op override for the other
23213
+ * (#1318 Correction 20).
23214
+ */
23215
+ function applyFooterBackgroundOverride(theme, footer, override) {
23216
+ if (override.kind === "default") return footer;
23217
+ const background = siteChromeColorBackground(override.token);
23218
+ const foreground = resolveChromeForegroundToken(theme, background);
23219
+ return {
23220
+ ...footer,
23221
+ background: {
23222
+ ...background,
23223
+ textColor: foreground
23224
+ },
23225
+ navColor: foreground,
23226
+ ...footer.navLinkStyle ? { navLinkStyle: {
23227
+ ...footer.navLinkStyle,
23228
+ colorToken: foreground
23229
+ } } : {},
23230
+ ...footer.bottomTextLinkStyle ? { bottomTextLinkStyle: {
23231
+ ...footer.bottomTextLinkStyle,
23232
+ colorToken: foreground
23233
+ } } : {},
23234
+ ...footer.bottomBar ? { bottomBar: {
23235
+ ...footer.bottomBar,
23236
+ textColor: foreground
23237
+ } } : {}
23238
+ };
23239
+ }
23240
+ function resolveChromeForegroundToken(theme, background) {
23241
+ const resolved = resolveSurfaceForeground(background, expandPalette(theme.palette));
23242
+ if (resolved.kind === "unknown") {
23243
+ throw new Error(`Site chrome background "${background?.type === "color" ? background.color ?? "unknown" : "unknown"}" has no resolvable AA foreground; the theme palette is incomplete`);
23244
+ }
23245
+ return resolved.roles.text;
23246
+ }
23247
+ function siteChromeColorBackground(color) {
23248
+ return {
23249
+ type: "color",
23250
+ contentOwner: siteChromeBackgroundOverrideOwner,
23251
+ color
23252
+ };
23253
+ }
23254
+ function toSiteChromeBackgroundColorTokens(tokenNames) {
23255
+ return tokenNames.flatMap((name) => {
23256
+ const token = parseSiteChromeBackgroundColorToken(name);
23257
+ return token ? [token] : [];
23258
+ });
23259
+ }
23260
+ function requiredQuickSiteChromeBackgroundColorToken(index) {
23261
+ const token = quickSiteChromeBackgroundColorTokens[index];
23262
+ if (!token) {
23263
+ throw new Error(`Missing quick site chrome background token at index ${index}`);
23264
+ }
23265
+ return token;
23266
+ }
23267
+
23268
+ //#endregion
23269
+ //#region ../theme-core/src/site-styles/siteChromeNavPreferences.ts
23270
+ const headerNavTypographyPreferenceValues = [
23271
+ "inherit",
23272
+ "body",
23273
+ "heading"
23274
+ ];
23275
+ const headerNavFontSizePreferenceValues = [
23276
+ "inherit",
23277
+ "sm",
23278
+ "base",
23279
+ "lg",
23280
+ "xl"
23281
+ ];
23282
+ const headerNavTypographyOptions = [
23283
+ {
23284
+ value: "inherit",
23285
+ label: "Default"
23286
+ },
23287
+ {
23288
+ value: "body",
23289
+ label: "Body"
23290
+ },
23291
+ {
23292
+ value: "heading",
23293
+ label: "Heading"
23294
+ }
23295
+ ];
23296
+ const headerNavFontSizeOptions = [
23297
+ {
23298
+ value: "inherit",
23299
+ label: "Default"
23300
+ },
23301
+ {
23302
+ value: "sm",
23303
+ label: "Small"
23304
+ },
23305
+ {
23306
+ value: "base",
23307
+ label: "Regular"
23308
+ },
23309
+ {
23310
+ value: "lg",
23311
+ label: "Large"
23312
+ },
23313
+ {
23314
+ value: "xl",
23315
+ label: "Huge"
23316
+ }
23317
+ ];
23318
+ const defaultHeaderNavPreferences = {
23319
+ typography: "inherit",
23320
+ fontSize: "inherit",
23321
+ interactionPalette: "recommended"
23322
+ };
23323
+ function readHeaderNavPreferences(theme) {
23324
+ const typography = theme?.header?.navFontRole;
23325
+ const fontSize = theme?.header?.navTextSize;
23326
+ const interactionPalette = readHeaderInteractionPalettePreference(theme?.header?.interactionPalette);
23327
+ return typography === "body" || typography === "heading" ? {
23328
+ typography,
23329
+ fontSize: readHeaderNavFontSizePreference(fontSize),
23330
+ interactionPalette
23331
+ } : {
23332
+ ...defaultHeaderNavPreferences,
23333
+ fontSize: readHeaderNavFontSizePreference(fontSize),
23334
+ interactionPalette
23335
+ };
23336
+ }
23337
+ /**
23338
+ * Closes over the palette vocabulary rather than trusting the stored value: a
23339
+ * persisted header is `jsonb` and can carry anything, and a bare `?? ` would
23340
+ * pass a malformed value straight through as if it were a preference.
23341
+ */
23342
+ function readHeaderInteractionPalettePreference(value) {
23343
+ return headerInteractionPaletteValues.find((palette) => palette === value) ?? "recommended";
23344
+ }
23345
+ function headerNavPreferencesEqual(left, right) {
23346
+ return left.typography === right.typography && left.fontSize === right.fontSize && left.interactionPalette === right.interactionPalette;
23347
+ }
23348
+ function withHeaderNavThemePreferences(theme, preferences) {
23349
+ const { navFontRole: _navFontRole, navTextSize: _navTextSize, interactionPalette: _interactionPalette, ...headerWithoutPreference } = theme.header;
23350
+ const nextHeader = {
23351
+ ...headerWithoutPreference,
23352
+ ...preferences.typography === "inherit" ? {} : { navFontRole: preferences.typography },
23353
+ ...preferences.fontSize === "inherit" ? {} : { navTextSize: preferences.fontSize },
23354
+ ...preferences.interactionPalette === "recommended" ? {} : { interactionPalette: preferences.interactionPalette }
23355
+ };
23356
+ return {
23357
+ ...theme,
23358
+ header: nextHeader
23359
+ };
23360
+ }
23361
+ /**
23362
+ * Applies the user's nav font-role/size overrides on top of a style-resolved
23363
+ * nav link style. This is where the precedence contract lands:
23364
+ *
23365
+ * - The nav typography TREATMENT (case/tracking identity: plain / heading /
23366
+ * quiet-caps / brand-caps) is ALWAYS owned by the active header style and is
23367
+ * baked into `style` before this call (see `applyNavTypography` in
23368
+ * headerLayoutStyles.ts).
23369
+ * - Each treatment resolves through a DEFAULT font role: `heading` resolves
23370
+ * through the heading role; `plain` / `quiet-caps` / `brand-caps` resolve
23371
+ * through the body role.
23372
+ * - The user's `navFontRole` (surfaced here as `preferences.typography`) only
23373
+ * overrides which font ROLE the treatment resolves through. It never changes
23374
+ * the treatment. `'inherit'` keeps the treatment's default role.
23375
+ */
23376
+ function mergeHeaderNavTypographyPreference(style, preferences) {
23377
+ return preferences.typography === "inherit" && preferences.fontSize === "inherit" ? style : {
23378
+ ...style,
23379
+ ...preferences.typography === "inherit" ? {} : { typography: preferences.typography },
23380
+ ...preferences.fontSize === "inherit" ? {} : { textSize: preferences.fontSize }
23381
+ };
23382
+ }
23383
+ function readHeaderNavFontSizePreference(value) {
23384
+ return value === "sm" || value === "base" || value === "lg" || value === "xl" ? value : "inherit";
23385
+ }
23386
+ /**
23387
+ * The nav size a header actually renders at, from either of the two places it
23388
+ * can be written.
23389
+ *
23390
+ * `generateHeaderCss` merges `header.navTextSize` over `navLinkStyle.textSize`
23391
+ * at render time (`mergeHeaderNavTypographyPreference`), so the marker field
23392
+ * wins when it is present and the compiled nav link style supplies the size
23393
+ * otherwise. Reading only the marker — which is what
23394
+ * `readHeaderNavPreferences` does, and correctly, because it answers "what does
23395
+ * the control show" — misses a size that reached the row through the compiled
23396
+ * bytes instead.
23397
+ */
23398
+ function headerNavEffectiveFontSize(header) {
23399
+ const marker = readHeaderNavFontSizePreference(header.navTextSize);
23400
+ return marker === "inherit" ? readHeaderNavFontSizePreference(header.navLinkStyle?.textSize) : marker;
23401
+ }
23402
+ /**
23403
+ * `navTextSize` derived as a preference by baseline comparison (#1318 Phase 4c
23404
+ * decision 1) — the same rule the structure fields already use: compare the
23405
+ * persisted value against what the row's own (layout, recipe) compiles, emit
23406
+ * `'inherit'` when they agree and the explicit value when they do not.
23407
+ *
23408
+ * The reason it needs the rule at all is that nav size is one of the fields a
23409
+ * pre-preferences row could only express by carrying it in the compiled bytes:
23410
+ * `heart-led-yoga` and `therese-millar-yoga` persist `navLinkStyle.textSize:
23411
+ * 'lg'` under a `serif-line` recipe that compiles no size, which is a user's
23412
+ * choice sitting in the wrong place. Reading only `header.navTextSize` derives
23413
+ * `'inherit'` there and a recompile would shrink their nav.
23414
+ *
23415
+ * Backfilling the derived value is pixel-preserving because the CSS merge above
23416
+ * makes `{navTextSize: 'lg'}` over a size-less nav link style render exactly
23417
+ * what `{navLinkStyle: {textSize: 'lg'}}` renders.
23418
+ *
23419
+ * **Two cases lose information, and both are accepted rather than accidental.**
23420
+ * The preference vocabulary is `inherit | sm | base | lg | xl` — there is no
23421
+ * value meaning "no size at all" — so `'inherit'` has to carry both of them:
23422
+ *
23423
+ * 1. *Persisted size equals the baseline.* Nothing in the bytes distinguishes
23424
+ * "chose the size the recipe already compiles" from "never chose", so the
23425
+ * marker field is dropped on recompile. **Pixel-neutral**: the recipe puts
23426
+ * the same size back.
23427
+ * 2. *Persisted has no size and the baseline compiles one.* The row renders at
23428
+ * the browser default and the recipe wants, say, `'sm'`; `'inherit'` is the
23429
+ * only representable answer, so **the recompile adopts the baseline size and
23430
+ * the row's sizeless-ness is lost**. This one is *not* pixel-neutral, and
23431
+ * representing it properly would mean adding a `'none'` arm to the
23432
+ * preference the way `HeaderStructurePreferences.logoText` has one.
23433
+ *
23434
+ * Case 2 is not silent. Adopting the baseline size is a byte change, so such a
23435
+ * row shows up as divergent in the Phase 4c report (`navLinkStyle.textSize
23436
+ * undefined -> "sm"`) and cannot reach Phase 4e unclassified — the report's
23437
+ * zero-rows gate is the safety net for exactly this. No row in either corpus is
23438
+ * in that state today; if one appears, it is a classification decision (widen
23439
+ * the vocabulary, or accept the size) rather than something this function may
23440
+ * guess at.
23441
+ */
23442
+ function readHeaderNavFontSizeAgainstBaseline(persisted, compiled) {
23443
+ const size = headerNavEffectiveFontSize(persisted);
23444
+ return size === headerNavEffectiveFontSize(compiled) ? "inherit" : size;
23445
+ }
23446
+
23447
+ //#endregion
23448
+ //#region ../theme-core/src/site-styles/headerBrandPresentation.ts
23449
+ /**
23450
+ * Whether the header wears a brand presentation at all (#1318 Phase 4c).
23451
+ *
23452
+ * `applyHeaderBrandPresentation` is unconditional today: given a logo context
23453
+ * it always writes a `logo` object carrying a resolved display, size and
23454
+ * accommodation. That makes "this header has no brand presentation block" —
23455
+ * which is what several live sites persist, including one whose site *does*
23456
+ * have a logo asset — a state the preference record could not express, so a
23457
+ * recompile would silently give the site a logo treatment it has never
23458
+ * rendered.
23459
+ *
23460
+ * This is the third field in this phase with that shape, after the interaction
23461
+ * palette and the nav text size: the persisted bytes carry intent the reader
23462
+ * cannot say, so the recompile "corrects" it. The fix is the same one — widen
23463
+ * the closed vocabulary with a named member and derive it by baseline
23464
+ * comparison — and it is a *widening*, not a restyle: `'recommended'` is the
23465
+ * default and reproduces today's behaviour exactly, so no row whose bytes carry
23466
+ * a presentation compiles differently for `'plain'` existing.
23467
+ *
23468
+ * `'plain'` is deliberately not "preserve whatever bytes are there". It is a
23469
+ * constructive instruction — *write no presentation block* — so Phase 4e's
23470
+ * compiler, which builds the header from nothing, can produce it.
23471
+ */
23472
+ const headerBrandPresentationValues = ["recommended", "plain"];
23473
+ const defaultHeaderBrandPresentation = "recommended";
23474
+ /**
23475
+ * Key-order-safe structural equality over the persisted `logo` object.
23476
+ *
23477
+ * The persisted value comes back from `jsonb`, whose key order is not the order
23478
+ * the applier builds; a `JSON.stringify` comparison would read two equal logo
23479
+ * blocks as different and quietly classify a `'recommended'` row as
23480
+ * unrecognised — the same trap the interaction-palette transcription test
23481
+ * exists to catch.
23482
+ */
23483
+ function jsonEquals(left, right) {
23484
+ if (left === right) return true;
23485
+ if (left === null || right === null) return false;
23486
+ if (typeof left !== "object" || typeof right !== "object") return false;
23487
+ if (Array.isArray(left) !== Array.isArray(right)) return false;
23488
+ if (Array.isArray(left) && Array.isArray(right)) {
23489
+ return left.length === right.length && left.every((value, index) => jsonEquals(value, right[index]));
23490
+ }
23491
+ const leftRecord = left;
23492
+ const rightRecord = right;
23493
+ const keys = new Set([...Object.keys(leftRecord), ...Object.keys(rightRecord)]);
23494
+ for (const key of keys) {
23495
+ if (!jsonEquals(leftRecord[key], rightRecord[key])) return false;
23496
+ }
23497
+ return true;
23498
+ }
23499
+ /**
23500
+ * The two keys the brand appliers write with a documented `'auto'` default, so
23501
+ * a persisted block that omits them means the same thing as one that spells
23502
+ * them out. This is the "implicit brand defaults made explicit" class the 4a
23503
+ * sweep pinned; treating it as a difference here would classify two live rows
23504
+ * as unrecognised over a value nobody chose and nothing renders differently.
23505
+ */
23506
+ function withImplicitBrandDefaults(logo) {
23507
+ return {
23508
+ ...logo,
23509
+ brandDisplay: logo.brandDisplay ?? "auto",
23510
+ size: logo.size ?? "auto"
23511
+ };
23512
+ }
23513
+ /**
23514
+ * Equal modulo the implicit brand defaults, and `null`-tolerant on both sides:
23515
+ * an absent block and an absent block are the same answer.
23516
+ */
23517
+ function logoBytesEqual(left, right) {
23518
+ const leftAbsent = left === undefined || left === null;
23519
+ const rightAbsent = right === undefined || right === null;
23520
+ if (leftAbsent || rightAbsent) return leftAbsent && rightAbsent;
23521
+ return jsonEquals(withImplicitBrandDefaults(left), withImplicitBrandDefaults(right));
23522
+ }
23523
+ /**
23524
+ * Which mode reproduces this row's persisted logo bytes.
23525
+ *
23526
+ * Both candidates are supplied rather than reconstructed here, because both are
23527
+ * the compilation's own output and rebuilding either would be a second
23528
+ * authority on what the modes mean. `'recommended'` is tested first so that a
23529
+ * row the resolver already agrees with keeps the live mode; `'plain'` is the
23530
+ * answer for the rows that carry no presentation — an absent block, or a block
23531
+ * holding only the recipe's own brand typography.
23532
+ */
23533
+ function classifyHeaderBrandPresentationBytes(persisted, recommended, plain) {
23534
+ if (logoBytesEqual(persisted.logo, recommended.logo)) {
23535
+ return { kind: "recommended" };
23536
+ }
23537
+ if (logoBytesEqual(persisted.logo, plain.logo)) return { kind: "plain" };
23538
+ return { kind: "unrecognised" };
23539
+ }
23540
+ /**
23541
+ * The preference the bytes express, or `null` when they express none the record
23542
+ * can carry. `null` is the caller's cue to fall back to the default and say so.
23543
+ */
23544
+ function readHeaderBrandPresentationFromBytes(persisted, recommended, plain) {
23545
+ const classification = classifyHeaderBrandPresentationBytes(persisted, recommended, plain);
23546
+ return classification.kind === "unrecognised" ? null : classification.kind;
23547
+ }
23548
+
23549
+ //#endregion
23550
+ //#region ../theme-core/src/site-styles/headerPreferences.ts
23551
+ const defaultHeaderStructurePreferences = {
23552
+ positioning: "inherit",
23553
+ shrinkOnScroll: "inherit",
23554
+ logoText: "inherit"
23555
+ };
23556
+ const defaultHeaderPreferences = {
23557
+ brandPresentation: defaultHeaderBrandPresentation,
23558
+ brandDisplay: defaultHeaderBrandPreferences.brandDisplay,
23559
+ logoSize: defaultHeaderBrandPreferences.logoSize,
23560
+ navFontRole: defaultHeaderNavPreferences.typography,
23561
+ navTextSize: defaultHeaderNavPreferences.fontSize,
23562
+ interactionPalette: defaultHeaderNavPreferences.interactionPalette,
23563
+ background: { kind: "default" },
23564
+ structure: defaultHeaderStructurePreferences
23565
+ };
23566
+ /**
23567
+ * Field-wise rather than `JSON.stringify`: the persisted value comes back from
23568
+ * `jsonb` and the compiled one is built by the interpreter, so their key order
23569
+ * is not guaranteed to match and a string comparison would report two equal
23570
+ * records as different.
23571
+ *
23572
+ * The destructure is exhaustive on purpose — adding a field to
23573
+ * `headerLogoTextSchema` without deciding whether it participates in this
23574
+ * comparison breaks the build here rather than silently collapsing two
23575
+ * different records to `'inherit'`.
23576
+ *
23577
+ * Note the asymmetry this leaves at the edge: a persisted `{}` against a
23578
+ * compiled `undefined` is *not* equal, so it derives to an explicit `{}`
23579
+ * rather than to `'none'`. That is deliberate — the row does carry a logo-text
23580
+ * record, it just carries an empty one — but it means `'none'` means "no
23581
+ * record at all", never "a record with nothing in it".
23582
+ */
23583
+ function headerLogoTextEquals(left, right) {
23584
+ if (left === undefined || right === undefined) return left === right;
23585
+ const comparisons = {
23586
+ mobileWrapLines: left.mobileWrapLines === right.mobileWrapLines,
23587
+ mobileMaxWidth: left.mobileMaxWidth === right.mobileMaxWidth,
23588
+ hideOnShrink: left.hideOnShrink === right.hideOnShrink
23589
+ };
23590
+ return Object.values(comparisons).every((equal) => equal);
23591
+ }
23592
+ /**
23593
+ * Brand presentation runs *after* the structure preferences and forces
23594
+ * `shrinkOnScroll: true` for a display-size logo on a backing accommodation
23595
+ * over a non-static header (`applyHeaderBrandPresentation` below). On such a
23596
+ * row the persisted `true` therefore carries no intent — it would be written
23597
+ * whatever the user had chosen — so the field is not derivable there and
23598
+ * collapses to `'inherit'`.
23599
+ *
23600
+ * This reads the row's own persisted logo rather than a live logo context, so
23601
+ * it holds for callers that have no context to supply (the divergence report,
23602
+ * and 4c's backfill). A caller that *does* supply the presentation gets the
23603
+ * same answer through the baseline, which already carries the forced value.
23604
+ */
23605
+ function headerBrandPresentationForcesShrink(header) {
23606
+ const accommodation = header.logo?.accommodation;
23607
+ return header.logo?.size === "display" && accommodation !== undefined && logoAccommodationProvidesBacking(accommodation) && header.positioning !== "static";
23608
+ }
23609
+ /**
23610
+ * The three structure fields are read back out of `jsonb`, so each one is
23611
+ * validated against its own vocabulary rather than trusted. A malformed value
23612
+ * expresses no intent, so it derives to `'inherit'` — never to a default that
23613
+ * would then be persisted as if the user had chosen it.
23614
+ */
23615
+ function readPersistedPositioning(value) {
23616
+ return headerPositioningValues.find((candidate$1) => candidate$1 === value) ?? null;
23617
+ }
23618
+ function readPersistedShrinkOnScroll(value) {
23619
+ return typeof value === "boolean" ? value : null;
23620
+ }
23621
+ /** "Present, but not a value this field's vocabulary accepts." */
23622
+ const invalidField = Symbol("invalid persisted field");
23623
+ const logoTextWrapLines = [1, 2];
23624
+ const logoTextMobileMaxWidths = [
23625
+ "xs",
23626
+ "sm",
23627
+ "md"
23628
+ ];
23629
+ function readOptionalMember(value, allowed) {
23630
+ if (value === undefined) return undefined;
23631
+ return allowed.find((candidate$1) => candidate$1 === value) ?? invalidField;
23632
+ }
23633
+ function readOptionalBoolean(value) {
23634
+ if (value === undefined) return undefined;
23635
+ return typeof value === "boolean" ? value : invalidField;
23636
+ }
23637
+ function readPersistedLogoText(value) {
23638
+ if (value === undefined) return { kind: "absent" };
23639
+ if (typeof value !== "object" || value === null) return { kind: "invalid" };
23640
+ const wrapLines = readOptionalMember(value.mobileWrapLines, logoTextWrapLines);
23641
+ const maxWidth = readOptionalMember(value.mobileMaxWidth, logoTextMobileMaxWidths);
23642
+ const hide = readOptionalBoolean(value.hideOnShrink);
23643
+ if (wrapLines === invalidField || maxWidth === invalidField || hide === invalidField) {
23644
+ return { kind: "invalid" };
23645
+ }
23646
+ return {
23647
+ kind: "present",
23648
+ value: {
23649
+ ...wrapLines === undefined ? {} : { mobileWrapLines: wrapLines },
23650
+ ...maxWidth === undefined ? {} : { mobileMaxWidth: maxWidth },
23651
+ ...hide === undefined ? {} : { hideOnShrink: hide }
23652
+ }
23653
+ };
23654
+ }
23655
+ function deriveLogoTextPreference(persisted, compiled) {
23656
+ const read = readPersistedLogoText(persisted);
23657
+ switch (read.kind) {
23658
+ case "invalid": return "inherit";
23659
+ case "absent": return compiled === undefined ? "inherit" : "none";
23660
+ case "present": return headerLogoTextEquals(read.value, compiled) ? "inherit" : read.value;
23661
+ }
23662
+ }
23663
+ /**
23664
+ * The written-down backfill rule (#1318 Phase 4a): compare the persisted value
23665
+ * against the value the row's own (layout, recipe) compiles, and emit
23666
+ * `'inherit'` when they are equal, the explicit value when they are not.
23667
+ *
23668
+ * Equal-to-default therefore collapses to `'inherit'`. Nothing in the data
23669
+ * distinguishes a deliberate "sticky, same as the default" from an untouched
23670
+ * row, so the backfill does not pretend to recover it; going forward the editor
23671
+ * records what the user picks and the distinction is real for new intent.
23672
+ *
23673
+ * Two qualifications on "the value it compiles", both of which keep the rule
23674
+ * from inventing intent:
23675
+ *
23676
+ * 1. A value that no vocabulary accepts expresses nothing, so it derives to
23677
+ * `'inherit'` rather than to whatever a fallback would have picked.
23678
+ * 2. A field a later pipeline step overwrites unconditionally cannot carry
23679
+ * intent at all. That is `shrinkOnScroll` on a display-logo row — see
23680
+ * `headerBrandPresentationForcesShrink`.
23681
+ */
23682
+ function readHeaderStructurePreferences(persisted, compiled) {
23683
+ const positioning = readPersistedPositioning(persisted.positioning);
23684
+ const shrinkOnScroll = readPersistedShrinkOnScroll(persisted.shrinkOnScroll);
23685
+ return {
23686
+ positioning: positioning === null || positioning === compiled.positioning ? "inherit" : positioning,
23687
+ shrinkOnScroll: shrinkOnScroll === null || shrinkOnScroll === compiled.shrinkOnScroll || headerBrandPresentationForcesShrink(persisted) ? "inherit" : shrinkOnScroll,
23688
+ logoText: deriveLogoTextPreference(persisted.logoText, compiled.logoText)
23689
+ };
23690
+ }
23691
+ /**
23692
+ * Total over any persisted theme, including a malformed one: every field goes
23693
+ * through a reader that closes over the vocabulary rather than trusting the
23694
+ * stored value.
23695
+ *
23696
+ * `compiledHeader` is the structure baseline — the header this row's own
23697
+ * (layout, recipe) compiles. It is a required parameter rather than something
23698
+ * derived inside, because a `Theme` alone does not carry the selection ids the
23699
+ * baseline needs; asking the caller for it keeps the rule single-valued.
23700
+ *
23701
+ * `navTextSize` is derived against that same baseline rather than read off
23702
+ * `header.navTextSize` (#1318 Phase 4c decision 1). It is the one nav field a
23703
+ * pre-preferences row could express only through the compiled bytes, so
23704
+ * reading the marker alone loses it — see
23705
+ * `readHeaderNavFontSizeAgainstBaseline`. `navFontRole` stays a marker read:
23706
+ * unlike size, `navLinkStyle.typography` is written by the recipe's own
23707
+ * treatment on every row, so a baseline comparison there would report the
23708
+ * treatment's default role as user intent.
23709
+ *
23710
+ * `interactionPaletteGroup` is the palette the row's resolved recipe offers,
23711
+ * and the same reasoning applies to it (#1318 Phase 4c decision 2): a row that
23712
+ * predates the palette carries its hover treatment in the compiled bytes and
23713
+ * nothing in `header.interactionPalette`. `undefined` means the caller does not
23714
+ * know which palette applies — it is not "the style has none" — and the marker
23715
+ * read stands.
23716
+ *
23717
+ * `brandPresentationCandidates` are the two headers the two presentation modes
23718
+ * produce for this row. The mode is derived by comparing the persisted `logo`
23719
+ * against both, so a row that carries no presentation block keeps having none
23720
+ * instead of being given one it has never rendered (#1318 Phase 4c decision
23721
+ * 5b). They are supplied rather than rebuilt here because they are the
23722
+ * compilation's own output, and a second construction of them would be a second
23723
+ * authority on what the modes mean — `headerBrandPresentationCandidates` builds
23724
+ * them with the real appliers.
23725
+ */
23726
+ function readHeaderPreferences(theme, compiledHeader, interactionPaletteGroup, brandPresentationCandidates) {
23727
+ const nav = readHeaderNavPreferences(theme);
23728
+ const brand = headerBrandPreferencesFromTheme(theme);
23729
+ const brandPresentation = readHeaderBrandPresentationFromBytes(theme.header, brandPresentationCandidates.recommended, brandPresentationCandidates.plain) ?? defaultHeaderBrandPresentation;
23730
+ return {
23731
+ brandPresentation,
23732
+ brandDisplay: brand.brandDisplay,
23733
+ logoSize: brand.logoSize,
23734
+ navFontRole: nav.typography,
23735
+ navTextSize: readHeaderNavFontSizeAgainstBaseline(theme.header, compiledHeader),
23736
+ interactionPalette: readHeaderInteractionPaletteFromBytes(theme.header, interactionPaletteGroup) ?? nav.interactionPalette,
23737
+ background: siteChromeBackgroundOverrideFromHeader(theme.header),
23738
+ structure: readHeaderStructurePreferences(theme.header, brandPresentationCandidates[brandPresentation])
23739
+ };
23740
+ }
23741
+ /**
23742
+ * Both candidates, built with the same two appliers the pipeline uses.
23743
+ *
23744
+ * There is no circularity to resolve: the only record fields that reach
23745
+ * `header.logo` are `brandDisplay` and `logoSize`, and those are read straight
23746
+ * off the persisted theme rather than derived. The structure preferences run
23747
+ * before the presentation in the named order but touch `positioning`,
23748
+ * `shrinkOnScroll` and `logoText` — never `logo` — so leaving them out here
23749
+ * cannot change either answer.
23750
+ *
23751
+ * One known over-specification, recorded rather than fixed because it changes
23752
+ * no bytes. `applyHeaderBrandPresentation` reads `positioning` when it decides
23753
+ * whether to force `shrinkOnScroll` for a display logo, and it sees the
23754
+ * *structure-adjusted* value at apply time while it sees the layout default
23755
+ * here. On a `'recommended'` row that overrides positioning to `'static'` the
23756
+ * candidate can therefore carry a forced `shrinkOnScroll` the applied header
23757
+ * will not, which makes `readHeaderStructurePreferences` emit an explicit
23758
+ * `shrinkOnScroll` where `'inherit'` would have done. The record is more
23759
+ * specific than it needs to be; the compiled bytes are identical either way,
23760
+ * because the explicit value it writes is the one the row already has. Fixing
23761
+ * it means deriving `positioning` before the candidates, which is a second
23762
+ * derivation pass for no byte change (#1318 Phase 4c review round 2, item 10).
23763
+ */
23764
+ function headerBrandPresentationCandidates(theme, compiledBaseline, presentation) {
23765
+ const brand = headerBrandPreferencesFromTheme(theme);
23766
+ const withBrand = (header) => withHeaderBrandThemePreferences({
23767
+ ...theme,
23768
+ header
23769
+ }, brand).header;
23770
+ return {
23771
+ recommended: withBrand(presentation === null ? compiledBaseline : applyHeaderBrandPresentation(compiledBaseline, presentation)),
23772
+ plain: withBrand(compiledBaseline)
23773
+ };
23774
+ }
23775
+ function applyHeaderStructurePreferences(header, preferences) {
23776
+ const { logoText: currentLogoText, ...headerWithoutLogoText } = header;
23777
+ return {
23778
+ ...headerWithoutLogoText,
23779
+ ...preferences.positioning === "inherit" ? {} : { positioning: preferences.positioning },
23780
+ ...preferences.shrinkOnScroll === "inherit" ? {} : { shrinkOnScroll: preferences.shrinkOnScroll },
23781
+ ...logoTextField(currentLogoText, preferences.logoText)
23782
+ };
23783
+ }
23784
+ function logoTextField(current, preference) {
23785
+ if (preference === "inherit") {
23786
+ return current === undefined ? {} : { logoText: current };
23787
+ }
23788
+ if (preference === "none") return {};
23789
+ return { logoText: preference };
23790
+ }
23791
+ /**
23792
+ * The brand presentation step, which sits between the structure preferences and
23793
+ * the nav/logo/background ones because it reads `positioning` and can force
23794
+ * `shrinkOnScroll` for a display logo (`previewCompiler.ts`).
23795
+ */
23796
+ /**
23797
+ * Writes a resolved brand presentation onto the header. Lives here rather than
23798
+ * in `previewCompiler` so the pipeline step has one definition: it sits between
23799
+ * the structure preferences and the nav/logo/background ones, and it is the
23800
+ * reason that position is load-bearing — it reads `positioning` and can force
23801
+ * `shrinkOnScroll` for a display logo that brings its own backing.
23802
+ */
23803
+ function applyHeaderBrandPresentation(header, presentation) {
23804
+ const shouldShrinkDisplayLogo = presentation.size === "display" && presentation.accommodation.providesBacking === true && header.positioning !== "static";
23805
+ return {
23806
+ ...header,
23807
+ shrinkOnScroll: shouldShrinkDisplayLogo ? true : header.shrinkOnScroll,
23808
+ logo: {
23809
+ fontFamily: header.logo?.fontFamily ?? "heading",
23810
+ letterSpacing: header.logo?.letterSpacing ?? "normal",
23811
+ gradient: header.logo?.gradient ?? false,
23812
+ brandDisplay: presentation.display,
23813
+ size: presentation.size,
23814
+ accommodation: presentation.accommodation.pattern
23815
+ }
23816
+ };
23817
+ }
23818
+ /**
23819
+ * The preference half of the header pipeline, composed once so that no caller
23820
+ * assembles its own order. Named order, tail of the recompile:
23821
+ *
23822
+ * structure preferences → brand presentation → brand display/logo size
23823
+ * → nav preferences → header background override
23824
+ *
23825
+ * `brandPresentation` is data rather than a callback so the step is inspectable
23826
+ * and cannot vary by caller; `null` is "this caller has no logo context".
23827
+ *
23828
+ * The background applier is the header-only one on purpose: the header+footer
23829
+ * pair function would clobber footer intent (#1318 Correction 20).
23830
+ */
23831
+ function applyHeaderPreferences(theme, preferences, brandPresentation) {
23832
+ const structured = applyHeaderStructurePreferences(theme.header, preferences.structure);
23833
+ const presented = brandPresentation === null || preferences.brandPresentation === "plain" ? structured : applyHeaderBrandPresentation(structured, brandPresentation);
23834
+ const branded = withHeaderBrandThemePreferences({
23835
+ ...theme,
23836
+ header: presented
23837
+ }, {
23838
+ brandDisplay: preferences.brandDisplay,
23839
+ logoSize: preferences.logoSize
23840
+ });
23841
+ const navved = withHeaderNavThemePreferences(branded, {
23842
+ typography: preferences.navFontRole,
23843
+ fontSize: preferences.navTextSize,
23844
+ interactionPalette: preferences.interactionPalette
23845
+ });
23846
+ return {
23847
+ ...navved,
23848
+ header: applyHeaderBackgroundOverride(navved, navved.header, preferences.background)
23849
+ };
23850
+ }
23851
+
23852
+ //#endregion
23853
+ //#region ../theme-core/src/site-styles/headerRecompile.ts
23854
+ /**
23855
+ * Curated repair followed by catalogue repair — the first two steps of the
23856
+ * header compilation pipeline, in one place so no caller re-derives them.
23857
+ */
23858
+ function resolveCuratedHeaderSelection(siteStyle, requested) {
23859
+ const requestedLayoutChoices = headerStyleChoicesForLayout(siteStyle, requested.layoutId);
23860
+ const curatedDefault = defaultHeaderCurationForSiteStyle(siteStyle);
23861
+ const resolvedLayoutId = requestedLayoutChoices ? requested.layoutId : curatedDefault.selection.layoutId;
23862
+ const curatedChoices = requestedLayoutChoices ?? curatedDefault.styles;
23863
+ const resolution = resolveHeaderLayoutStyleSelection({
23864
+ layoutId: resolvedLayoutId,
23865
+ styleId: deriveNearestCompatibleHeaderStyle(requested.styleId, resolvedLayoutId, curatedChoices.map((choice) => choice.id))
23866
+ });
23867
+ return {
23868
+ resolution,
23869
+ repair: resolution.selection.layoutId === requested.layoutId && resolution.selection.styleId === requested.styleId ? { kind: "exact" } : {
23870
+ kind: "repaired",
23871
+ reason: requestedLayoutChoices ? "style-not-curated-for-layout" : "layout-not-curated-for-site-style"
23872
+ }
23873
+ };
23874
+ }
23875
+ /**
23876
+ * Curated repair → catalogue repair → recipe → interaction palette → brand
23877
+ * presentation. Everything that runs before the preference record, in one
23878
+ * place: `previewCompiler.compileHeaderDirective` and the recompile below are
23879
+ * both callers, so the middle of the pipeline has a single composition.
23880
+ *
23881
+ * `baseline` and `header` are both returned because they answer different
23882
+ * questions. `header` is what a preview wants to render. `baseline` is what
23883
+ * `readHeaderPreferences` compares the persisted bytes against for every field
23884
+ * the presentation does not touch.
23885
+ *
23886
+ * The interaction-palette preference is threaded in through `theme.header`
23887
+ * because that is where `compileHeaderLayoutStyleSelection` reads it from
23888
+ * today; 4b can pass it explicitly once the preferences column exists.
23889
+ */
23890
+ function compileHeaderForCuratedSelection(theme, siteStyle, requested, requestedBrandPresentation, requestedInteractionPalette) {
23891
+ const { resolution, repair: repair$1 } = resolveCuratedHeaderSelection(siteStyle, requested);
23892
+ const interactionPalette = resolveHeaderStyleRecipe(siteStyle, resolution.style.id).interactionPalette;
23893
+ const baseline = compileHeaderLayoutStyleSelection(theme, resolution, siteStyle, resolveInteractionPaletteRequest(theme, interactionPalette, requestedInteractionPalette));
23894
+ const brandPresentation = requestedBrandPresentation.kind === "apply-logo-context" ? resolveHeaderBrandPresentationForLayout({
23895
+ layout: resolution.layout,
23896
+ logo: requestedBrandPresentation.logo
23897
+ }) : null;
23898
+ return {
23899
+ baseline,
23900
+ header: brandPresentation === null ? baseline : applyHeaderBrandPresentation(baseline, brandPresentation),
23901
+ brandPresentation,
23902
+ interactionPalette,
23903
+ resolution,
23904
+ repair: repair$1
23905
+ };
23906
+ }
23907
+ /**
23908
+ * The bytes-derived answer wins when the recipe offers a palette at all;
23909
+ * otherwise the marker field on the theme stands, which is what every caller
23910
+ * that holds no preference record has always used.
23911
+ */
23912
+ function resolveInteractionPaletteRequest(theme, group, request) {
23913
+ if (request.kind === "preference") return request.preference;
23914
+ return readHeaderInteractionPaletteFromBytes(theme.header, group) ?? readHeaderNavPreferences(theme).interactionPalette;
23915
+ }
23916
+ /**
23917
+ * The whole header pipeline, in one named function so 4b, 4c and 4d cannot
23918
+ * drift into three orders (#1318 Phase 4):
23919
+ *
23920
+ * curated repair → catalogue repair → recipe → layout defaults
23921
+ * → interaction palette → structure preferences → brand presentation
23922
+ * → nav/logo/background preferences
23923
+ */
23924
+ function recompileSiteChromeHeader(input) {
23925
+ return applyPreferencesToCompiledHeader(input.theme, compileHeaderForCuratedSelection(input.theme, input.siteStyle, input.requested, input.brandPresentation, {
23926
+ kind: "preference",
23927
+ preference: input.preferences.interactionPalette
23928
+ }), input.preferences);
23929
+ }
23930
+ /**
23931
+ * The preferences run against `baseline`, not `header`: the brand presentation
23932
+ * belongs *after* the structure preferences in the named order, and
23933
+ * `applyHeaderPreferences` applies it there. Starting from `header` would apply
23934
+ * it twice and put the earlier copy in the wrong place.
23935
+ */
23936
+ function applyPreferencesToCompiledHeader(theme, compiled, preferences) {
23937
+ const applied = applyHeaderPreferences({
23938
+ ...theme,
23939
+ header: compiled.baseline
23940
+ }, preferences, compiled.brandPresentation);
23941
+ return {
23942
+ header: applied.header,
23943
+ resolution: compiled.resolution,
23944
+ repair: compiled.repair
23945
+ };
23946
+ }
23947
+ /**
23948
+ * The read-then-recompile round trip: derive the row's intent from its own
23949
+ * persisted bytes, then rebuild the header from that intent. On a row whose
23950
+ * bytes are compiler output this returns the persisted header unchanged, which
23951
+ * is what the Phase 4c divergence report measures.
23952
+ */
23953
+ function recompileSiteChromeHeaderFromPersistedIntent(theme, siteStyle, requested, brandPresentation) {
23954
+ const compiled = compileHeaderForCuratedSelection(theme, siteStyle, requested, brandPresentation, { kind: "derive-from-persisted-bytes" });
23955
+ const candidates = headerBrandPresentationCandidates(theme, compiled.baseline, compiled.brandPresentation);
23956
+ const preferences = readHeaderPreferences(theme, compiled.header, compiled.interactionPalette, candidates);
23957
+ return {
23958
+ recompiled: applyPreferencesToCompiledHeader(theme, compiled, preferences),
23959
+ preferences,
23960
+ interactionPaletteBytes: classifyHeaderInteractionPaletteBytes(theme.header, compiled.interactionPalette),
23961
+ brandPresentationBytes: classifyHeaderBrandPresentationBytes(theme.header, candidates.recommended, candidates.plain)
23962
+ };
23963
+ }
23964
+
22481
23965
  //#endregion
22482
23966
  //#region ../theme-core/src/site-styles/runtimeBackgroundResolution.ts
22483
23967
  function parseCanonicalRuntimeBackground(content) {
@@ -22764,43 +24248,56 @@ function isCycleableDesignOption(option) {
22764
24248
  //#endregion
22765
24249
  //#region ../theme-core/src/site-styles/previewCompiler.ts
22766
24250
  const previewCustomizePurpose = "preview-customize";
24251
+ /**
24252
+ * "Leave the header as the user last chose it" (#1318 Phase 4d).
24253
+ *
24254
+ * Before 4d this returned the caller's bytes verbatim, which was a fourth path
24255
+ * by which a hand-written header reached persistence. It now recompiles from
24256
+ * the row's stored selection and preferences — the stored record when the
24257
+ * caller holds one, and the row's own bytes read back through the Phase 4a
24258
+ * reader while the Phase 4c backfill is outstanding. On a row whose bytes are
24259
+ * compiler output this returns the persisted header unchanged, which is what
24260
+ * the divergence report measures; on a row whose bytes diverge, the intent is
24261
+ * what survives, which is the point of the phase.
24262
+ */
24263
+ function compilePreservedHeaderDirective(directive, input) {
24264
+ const recompiled = directive.preferences === null ? recompileSiteChromeHeaderFromPersistedIntent(directive.persistedTheme, input.siteStyle, input.currentSelection, directive.brandPresentation).recompiled : recompileSiteChromeHeader({
24265
+ theme: directive.persistedTheme,
24266
+ siteStyle: input.siteStyle,
24267
+ requested: input.currentSelection,
24268
+ preferences: directive.preferences,
24269
+ brandPresentation: directive.brandPresentation
24270
+ });
24271
+ return {
24272
+ header: recompiled.header,
24273
+ brandPresentation: null,
24274
+ resolvedHeaderSelection: recompiled.resolution.selection,
24275
+ warning: recompiled.repair.kind === "exact" ? null : {
24276
+ kind: "repaired-header-selection",
24277
+ requested: input.currentSelection,
24278
+ resolved: recompiled.resolution.selection,
24279
+ reason: recompiled.repair.reason
24280
+ }
24281
+ };
24282
+ }
22767
24283
  function compileHeaderDirective(input) {
22768
24284
  if (input.directive.kind === "preserve") {
22769
- return {
22770
- header: input.directive.header,
22771
- brandPresentation: null,
22772
- resolvedHeaderSelection: input.currentSelection,
22773
- warning: null
22774
- };
24285
+ return compilePreservedHeaderDirective(input.directive, input);
22775
24286
  }
22776
24287
  const requested = input.directive.selection;
22777
- const requestedLayoutChoices = headerStyleChoicesForLayout(input.siteStyle, requested.layoutId);
22778
- const curatedDefault = defaultHeaderSelectionForSiteStyle(input.siteStyle);
22779
- const resolvedLayoutId = requestedLayoutChoices ? requested.layoutId : curatedDefault.layoutId;
22780
- const curatedChoices = requestedLayoutChoices ?? headerStyleChoicesForLayout(input.siteStyle, curatedDefault.layoutId);
22781
- if (!curatedChoices) {
22782
- throw new Error(`Site style ${input.siteStyle.id} has invalid header curation`);
22783
- }
22784
- const resolvedStyleId = deriveNearestCompatibleHeaderStyle(requested.styleId, resolvedLayoutId, curatedChoices.map((choice) => choice.id));
22785
- const resolution = resolveHeaderLayoutStyleSelection({
22786
- layoutId: resolvedLayoutId,
22787
- styleId: resolvedStyleId
24288
+ const { header, brandPresentation, resolution, repair: repair$1 } = compileHeaderForCuratedSelection(input.theme, input.siteStyle, requested, input.directive.brandPresentation, {
24289
+ kind: "preference",
24290
+ preference: readHeaderNavPreferences(input.theme).interactionPalette
22788
24291
  });
22789
- const header = compileHeaderLayoutStyleSelection(input.theme, resolution);
22790
- const brandPresentation = input.directive.brandPresentation.kind === "apply-logo-context" ? resolveHeaderBrandPresentationForLayout({
22791
- layout: resolution.layout,
22792
- logo: input.directive.brandPresentation.logo
22793
- }) : null;
22794
- const headerWithBrandPresentation = brandPresentation !== null ? applyHeaderBrandPresentation(header, brandPresentation) : header;
22795
24292
  return {
22796
- header: headerWithBrandPresentation,
24293
+ header,
22797
24294
  brandPresentation,
22798
24295
  resolvedHeaderSelection: resolution.selection,
22799
- warning: resolution.selection.layoutId === requested.layoutId && resolution.selection.styleId === requested.styleId ? null : {
24296
+ warning: repair$1.kind === "exact" ? null : {
22800
24297
  kind: "repaired-header-selection",
22801
24298
  requested,
22802
24299
  resolved: resolution.selection,
22803
- reason: requestedLayoutChoices ? "style-not-curated-for-layout" : "layout-not-curated-for-site-style"
24300
+ reason: repair$1.reason
22804
24301
  }
22805
24302
  };
22806
24303
  }
@@ -22834,21 +24331,6 @@ function compileIncrementalFooter(input) {
22834
24331
  resolvedFooterLookId: result.selectedLookId
22835
24332
  };
22836
24333
  }
22837
- function applyHeaderBrandPresentation(header, presentation) {
22838
- const shouldShrinkDisplayLogo = presentation.size === "display" && presentation.accommodation.providesBacking === true && header.positioning !== "static";
22839
- return {
22840
- ...header,
22841
- shrinkOnScroll: shouldShrinkDisplayLogo ? true : header.shrinkOnScroll,
22842
- logo: {
22843
- fontFamily: header.logo?.fontFamily ?? "heading",
22844
- letterSpacing: header.logo?.letterSpacing ?? "normal",
22845
- gradient: header.logo?.gradient ?? false,
22846
- brandDisplay: presentation.display,
22847
- size: presentation.size,
22848
- accommodation: presentation.accommodation.pattern
22849
- }
22850
- };
22851
- }
22852
24334
  function applyFooterBrandPresentation(footer, presentation) {
22853
24335
  const size = footerLogoSizeFromBrandPresentation(presentation.size);
22854
24336
  return {
@@ -23924,10 +25406,127 @@ function runGeneratedPageDesignPipeline(input) {
23924
25406
  };
23925
25407
  }
23926
25408
 
25409
+ //#endregion
25410
+ //#region ../theme-core/src/site-styles/headerPreferencesPersistence.ts
25411
+ const backgroundColorTokenSchema = z.unknown().transform((value, ctx) => {
25412
+ const token = parseSiteChromeBackgroundColorToken(value);
25413
+ if (token === null) {
25414
+ ctx.addIssue({
25415
+ code: "custom",
25416
+ message: "Unknown site chrome background colour token"
25417
+ });
25418
+ return z.NEVER;
25419
+ }
25420
+ return token;
25421
+ });
25422
+ const backgroundOverrideSchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("default") }).strict(), z.object({
25423
+ kind: z.literal("color"),
25424
+ token: backgroundColorTokenSchema
25425
+ }).strict()]);
25426
+ const logoTextIntentSchema = z.object({
25427
+ mobileMaxWidth: z.enum([
25428
+ "xs",
25429
+ "sm",
25430
+ "md"
25431
+ ]).optional(),
25432
+ mobileWrapLines: z.union([z.literal(1), z.literal(2)]).optional(),
25433
+ hideOnShrink: z.boolean().optional()
25434
+ }).strict();
25435
+ const structureSchema = z.object({
25436
+ positioning: z.union([z.literal("inherit"), z.enum(headerPositioningValues)]),
25437
+ shrinkOnScroll: z.union([z.literal("inherit"), z.boolean()]),
25438
+ logoText: z.union([
25439
+ z.literal("inherit"),
25440
+ z.literal("none"),
25441
+ logoTextIntentSchema
25442
+ ])
25443
+ }).strict();
25444
+ const headerPreferencesShape = {
25445
+ brandPresentation: z.enum(headerBrandPresentationValues),
25446
+ brandDisplay: z.enum(brandDisplayModeValues),
25447
+ logoSize: z.enum(logoSizePolicyValues),
25448
+ navFontRole: z.enum(headerNavTypographyPreferenceValues),
25449
+ navTextSize: z.enum(headerNavFontSizePreferenceValues),
25450
+ interactionPalette: z.union([z.literal("recommended"), z.enum(headerInteractionPaletteValues)]),
25451
+ background: backgroundOverrideSchema,
25452
+ structure: structureSchema
25453
+ };
25454
+ /**
25455
+ * The persisted shape of `HeaderPreferences`, closed at every field.
25456
+ *
25457
+ * `.strict()` throughout is deliberate: an unknown key in the stored blob means
25458
+ * the writer knew about a field this reader does not, and silently dropping it
25459
+ * would let a save round-trip away someone else's intent. Every field is
25460
+ * required for the same reason a default would be a hidden authority: a stored
25461
+ * record that omits one is not a record this reader may complete.
25462
+ */
25463
+ const headerPreferencesSchema = z.object(headerPreferencesShape).strict();
25464
+ /** Only controls the client can edit may cross the write boundary. */
25465
+ const headerPreferencesWireSchema = z.object(headerPreferencesShape).omit({
25466
+ brandPresentation: true,
25467
+ structure: true
25468
+ }).strict();
25469
+ function parsePersistedHeaderPreferences(value) {
25470
+ if (value === null || value === undefined) return { kind: "absent" };
25471
+ const parsed = headerPreferencesSchema.safeParse(value);
25472
+ if (parsed.success) return {
25473
+ kind: "present",
25474
+ preferences: parsed.data
25475
+ };
25476
+ return {
25477
+ kind: "invalid",
25478
+ issues: parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`)
25479
+ };
25480
+ }
25481
+
25482
+ //#endregion
25483
+ //#region ../theme-core/src/site-styles/headerBytesComparison.ts
25484
+ function isPlainObject(value) {
25485
+ return typeof value === "object" && value !== null && !Array.isArray(value);
25486
+ }
25487
+ /**
25488
+ * Structural diff by JSON path. Values are compared through
25489
+ * `JSON.stringify`, so a key present with the value `undefined` and an absent
25490
+ * key read as the same thing — which is what `jsonb` does to them anyway
25491
+ * (#1318 Correction 5).
25492
+ */
25493
+ function diffJsonPaths(left, right, path$2 = "") {
25494
+ if (isPlainObject(left) && isPlainObject(right)) {
25495
+ const keys = [...new Set([...Object.keys(left), ...Object.keys(right)])].sort();
25496
+ return keys.flatMap((key) => diffJsonPaths(left[key], right[key], path$2 ? `${path$2}.${key}` : key));
25497
+ }
25498
+ if (JSON.stringify(left) === JSON.stringify(right)) return [];
25499
+ return [{
25500
+ path: path$2,
25501
+ left,
25502
+ right
25503
+ }];
25504
+ }
25505
+ function roundTrip(header) {
25506
+ return JSON.parse(JSON.stringify(header));
25507
+ }
25508
+ /**
25509
+ * The Phase 4b equality assertion: does the header a client compiled match the
25510
+ * one the server compiles from the same `(selection, preferences)`?
25511
+ *
25512
+ * Both sides go through a JSON round trip first, because the persisted form is
25513
+ * `jsonb` and an in-memory `ctaVariant: undefined` is not a difference the
25514
+ * database could ever store (#1318 Correction 5). Comparing the in-memory
25515
+ * objects directly would report a mismatch that no save could produce.
25516
+ */
25517
+ function compareCompiledHeaders(left, right) {
25518
+ const diffs = diffJsonPaths(roundTrip(left), roundTrip(right));
25519
+ return diffs.length === 0 ? { kind: "equal" } : {
25520
+ kind: "different",
25521
+ diffs,
25522
+ differingPaths: diffs.map((diff) => diff.path)
25523
+ };
25524
+ }
25525
+
23927
25526
  //#endregion
23928
25527
  //#region ../theme-core/src/site-styles/headerOpeningForeground.ts
23929
- function headerStyleAvailability(style, context) {
23930
- if (!style.openingForegroundTarget) return { kind: "available" };
25528
+ function headerStyleAvailability(recipe, context) {
25529
+ if (!recipe.openingForeground) return { kind: "available" };
23931
25530
  return context?.openingForeground?.kind === "known" ? { kind: "available" } : {
23932
25531
  kind: "unavailable",
23933
25532
  reason: "requires-known-opening-foreground"
@@ -23939,16 +25538,16 @@ function headerStyleAvailabilityReasonLabel(reason) {
23939
25538
  default: return assertNever$5(reason, "Unhandled header style availability reason");
23940
25539
  }
23941
25540
  }
23942
- function applyOpeningForeground(theme, header, style, context) {
23943
- if (!style.openingForegroundTarget) return header;
25541
+ function applyOpeningForeground(theme, header, recipe, context) {
25542
+ if (!recipe.openingForeground) return header;
23944
25543
  const foreground = context?.openingForeground ?? { kind: "unknown" };
23945
25544
  if (foreground.kind === "unknown") return applyUnknownOpeningForegroundFallback(header);
23946
25545
  const withBrandForeground = {
23947
25546
  ...header,
23948
25547
  textColor: foreground.roles.text
23949
25548
  };
23950
- if (style.openingForegroundTarget === "brand-only") return withBrandForeground;
23951
- const scrollTreatment = compileHeaderScrollTreatment(theme, style, foreground.roles.text);
25549
+ if (recipe.openingForeground === "brand-only") return withBrandForeground;
25550
+ const scrollTreatment = compileHeaderScrollTreatment(theme, recipe, foreground.roles.text);
23952
25551
  return {
23953
25552
  ...withBrandForeground,
23954
25553
  navColor: foreground.roles.text,
@@ -23965,8 +25564,8 @@ function applyOpeningForeground(theme, header, style, context) {
23965
25564
  ...scrollTreatment ? { scrollTreatment } : {}
23966
25565
  };
23967
25566
  }
23968
- function compileHeaderScrollTreatment(theme, style, foregroundToken) {
23969
- if (style.scrollTreatment !== "contrast-glass") return undefined;
25567
+ function compileHeaderScrollTreatment(theme, recipe, foregroundToken) {
25568
+ if (recipe.scrollTreatment !== "contrast-glass") return undefined;
23970
25569
  const tokenParts = parseColorTokenParts(foregroundToken);
23971
25570
  if (tokenParts.kind !== "token") return undefined;
23972
25571
  const foregroundHex = expandPalette(theme.palette)[tokenParts.token];
@@ -24003,11 +25602,11 @@ function applyUnknownOpeningForegroundFallback(header) {
24003
25602
  container: UNKNOWN_OPENING_FOREGROUND_CONTAINER
24004
25603
  };
24005
25604
  }
24006
- function applyPageOpeningForegroundToTheme(theme, style, context) {
24007
- if (!style || !style.openingForegroundTarget) return theme;
25605
+ function applyPageOpeningForegroundToTheme(theme, recipe, context) {
25606
+ if (!recipe?.openingForeground) return theme;
24008
25607
  return {
24009
25608
  ...theme,
24010
- header: applyOpeningForeground(theme, theme.header, style, context)
25609
+ header: applyOpeningForeground(theme, theme.header, recipe, context)
24011
25610
  };
24012
25611
  }
24013
25612
 
@@ -24363,276 +25962,6 @@ function boundaryPinsEqual(left, right) {
24363
25962
  return left.boundaryId === right.boundaryId && left.previousBlockId === right.previousBlockId && left.nextBlockId === right.nextBlockId && boundaryOptionIdForTreatment(left.choice.value) === boundaryOptionIdForTreatment(right.choice.value);
24364
25963
  }
24365
25964
 
24366
- //#endregion
24367
- //#region ../theme-core/src/site-styles/siteChromeBackgrounds.ts
24368
- const siteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceColorTokenNames);
24369
- const quickSiteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceQuickColorTokenNames);
24370
- const advancedSiteChromeBackgroundColorTokens = siteChromeBackgroundColorTokens.filter((token) => !quickSiteChromeBackgroundColorTokens.includes(token));
24371
- const defaultSiteChromeBackgroundOverride = { kind: "default" };
24372
- function defaultSiteChromeBackgroundPreferences() {
24373
- return {
24374
- header: defaultSiteChromeBackgroundOverride,
24375
- footer: defaultSiteChromeBackgroundOverride
24376
- };
24377
- }
24378
- function parseSiteChromeBackgroundColorToken(value) {
24379
- const token = parseBackgroundAppearanceColorToken(value);
24380
- return token ? token : null;
24381
- }
24382
- function siteChromeBackgroundPreferencesFromTheme(theme) {
24383
- if (!theme) return defaultSiteChromeBackgroundPreferences();
24384
- return {
24385
- header: theme.header ? siteChromeBackgroundOverrideFromHeader(theme.header) : defaultSiteChromeBackgroundOverride,
24386
- footer: theme.footer ? siteChromeBackgroundOverrideFromFooter(theme.footer) : defaultSiteChromeBackgroundOverride
24387
- };
24388
- }
24389
- function applySiteChromeBackgroundPreferences(theme, preferences) {
24390
- return {
24391
- ...theme,
24392
- header: applyHeaderBackgroundOverride(theme, theme.header, preferences.header),
24393
- footer: applyFooterBackgroundOverride(theme, theme.footer, preferences.footer)
24394
- };
24395
- }
24396
- function canCustomizeSiteChromeBackground(theme, surface$1) {
24397
- if (surface$1 === "footer") return true;
24398
- const header = theme?.header;
24399
- if (!header) return false;
24400
- const navContainerType = header.navContainer?.type ?? "none";
24401
- return header.variant !== "floating" && navContainerType !== "glass" && navContainerType !== "pill";
24402
- }
24403
- function siteChromeBackgroundOverrideLabel(override) {
24404
- switch (override.kind) {
24405
- case "default": return "Default";
24406
- case "color": return formatSiteChromeBackgroundTokenLabel(override.token);
24407
- }
24408
- }
24409
- function formatSiteChromeBackgroundTokenLabel(token) {
24410
- return String(token).replace(/-/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
24411
- }
24412
- function siteChromeBackgroundOverrideEquals(left, right) {
24413
- return left.kind === right.kind && (left.kind === "default" || right.kind === "color" && left.token === right.token);
24414
- }
24415
- function cycleSiteChromeBackgroundOverride(current, direction) {
24416
- const fallback = direction === "next" ? quickSiteChromeBackgroundColorTokens[0] : quickSiteChromeBackgroundColorTokens[quickSiteChromeBackgroundColorTokens.length - 1];
24417
- if (!fallback) return defaultSiteChromeBackgroundOverride;
24418
- if (current.kind === "default") {
24419
- return {
24420
- kind: "color",
24421
- token: fallback
24422
- };
24423
- }
24424
- const currentIndex = quickSiteChromeBackgroundColorTokens.indexOf(current.token);
24425
- if (currentIndex === -1) {
24426
- return {
24427
- kind: "color",
24428
- token: fallback
24429
- };
24430
- }
24431
- const nextIndex = direction === "next" ? (currentIndex + 1) % quickSiteChromeBackgroundColorTokens.length : currentIndex === 0 ? quickSiteChromeBackgroundColorTokens.length - 1 : currentIndex - 1;
24432
- const token = requiredQuickSiteChromeBackgroundColorToken(nextIndex);
24433
- return {
24434
- kind: "color",
24435
- token
24436
- };
24437
- }
24438
- function siteChromeBackgroundOverrideFromHeader(header) {
24439
- return siteChromeBackgroundOverrideFromBackground(header.background);
24440
- }
24441
- function siteChromeBackgroundOverrideFromFooter(footer) {
24442
- return siteChromeBackgroundOverrideFromBackground(footer.background);
24443
- }
24444
- function siteChromeBackgroundOverrideFromBackground(background) {
24445
- if (background?.contentOwner !== siteChromeBackgroundOverrideOwner || background.type !== "color") {
24446
- return defaultSiteChromeBackgroundOverride;
24447
- }
24448
- const token = parseSiteChromeBackgroundColorToken(background.color);
24449
- return token ? {
24450
- kind: "color",
24451
- token
24452
- } : defaultSiteChromeBackgroundOverride;
24453
- }
24454
- function applyHeaderBackgroundOverride(theme, header, override) {
24455
- if (override.kind === "default") return header;
24456
- const background = siteChromeColorBackground(override.token);
24457
- const foreground = resolveChromeForegroundToken(theme, background);
24458
- return {
24459
- ...header,
24460
- background: {
24461
- ...background,
24462
- textColor: foreground
24463
- },
24464
- textColor: foreground,
24465
- navColor: foreground,
24466
- ...header.navLinkStyle ? { navLinkStyle: {
24467
- ...header.navLinkStyle,
24468
- colorToken: foreground
24469
- } } : {}
24470
- };
24471
- }
24472
- function applyFooterBackgroundOverride(theme, footer, override) {
24473
- if (override.kind === "default") return footer;
24474
- const background = siteChromeColorBackground(override.token);
24475
- const foreground = resolveChromeForegroundToken(theme, background);
24476
- return {
24477
- ...footer,
24478
- background: {
24479
- ...background,
24480
- textColor: foreground
24481
- },
24482
- navColor: foreground,
24483
- ...footer.navLinkStyle ? { navLinkStyle: {
24484
- ...footer.navLinkStyle,
24485
- colorToken: foreground
24486
- } } : {},
24487
- ...footer.bottomTextLinkStyle ? { bottomTextLinkStyle: {
24488
- ...footer.bottomTextLinkStyle,
24489
- colorToken: foreground
24490
- } } : {},
24491
- ...footer.bottomBar ? { bottomBar: {
24492
- ...footer.bottomBar,
24493
- textColor: foreground
24494
- } } : {}
24495
- };
24496
- }
24497
- function resolveChromeForegroundToken(theme, background) {
24498
- const resolved = resolveSurfaceForeground(background, expandPalette(theme.palette));
24499
- if (resolved.kind === "unknown") {
24500
- throw new Error(`Site chrome background "${background?.type === "color" ? background.color ?? "unknown" : "unknown"}" has no resolvable AA foreground; the theme palette is incomplete`);
24501
- }
24502
- return resolved.roles.text;
24503
- }
24504
- function siteChromeColorBackground(color) {
24505
- return {
24506
- type: "color",
24507
- contentOwner: siteChromeBackgroundOverrideOwner,
24508
- color
24509
- };
24510
- }
24511
- function toSiteChromeBackgroundColorTokens(tokenNames) {
24512
- return tokenNames.flatMap((name) => {
24513
- const token = parseSiteChromeBackgroundColorToken(name);
24514
- return token ? [token] : [];
24515
- });
24516
- }
24517
- function requiredQuickSiteChromeBackgroundColorToken(index) {
24518
- const token = quickSiteChromeBackgroundColorTokens[index];
24519
- if (!token) {
24520
- throw new Error(`Missing quick site chrome background token at index ${index}`);
24521
- }
24522
- return token;
24523
- }
24524
-
24525
- //#endregion
24526
- //#region ../theme-core/src/site-styles/siteChromeNavPreferences.ts
24527
- const headerNavTypographyPreferenceValues = [
24528
- "inherit",
24529
- "body",
24530
- "heading"
24531
- ];
24532
- const headerNavFontSizePreferenceValues = [
24533
- "inherit",
24534
- "sm",
24535
- "base",
24536
- "lg",
24537
- "xl"
24538
- ];
24539
- const headerNavTypographyOptions = [
24540
- {
24541
- value: "inherit",
24542
- label: "Default"
24543
- },
24544
- {
24545
- value: "body",
24546
- label: "Body"
24547
- },
24548
- {
24549
- value: "heading",
24550
- label: "Heading"
24551
- }
24552
- ];
24553
- const headerNavFontSizeOptions = [
24554
- {
24555
- value: "inherit",
24556
- label: "Default"
24557
- },
24558
- {
24559
- value: "sm",
24560
- label: "Small"
24561
- },
24562
- {
24563
- value: "base",
24564
- label: "Regular"
24565
- },
24566
- {
24567
- value: "lg",
24568
- label: "Large"
24569
- },
24570
- {
24571
- value: "xl",
24572
- label: "Huge"
24573
- }
24574
- ];
24575
- const defaultHeaderNavPreferences = {
24576
- typography: "inherit",
24577
- fontSize: "inherit",
24578
- interactionPalette: "recommended"
24579
- };
24580
- function readHeaderNavPreferences(theme) {
24581
- const typography = theme?.header?.navFontRole;
24582
- const fontSize = theme?.header?.navTextSize;
24583
- const interactionPalette = theme?.header?.interactionPalette ?? "recommended";
24584
- return typography === "body" || typography === "heading" ? {
24585
- typography,
24586
- fontSize: readHeaderNavFontSizePreference(fontSize),
24587
- interactionPalette
24588
- } : {
24589
- ...defaultHeaderNavPreferences,
24590
- fontSize: readHeaderNavFontSizePreference(fontSize),
24591
- interactionPalette
24592
- };
24593
- }
24594
- function headerNavPreferencesEqual(left, right) {
24595
- return left.typography === right.typography && left.fontSize === right.fontSize && left.interactionPalette === right.interactionPalette;
24596
- }
24597
- function withHeaderNavThemePreferences(theme, preferences) {
24598
- const { navFontRole: _navFontRole, navTextSize: _navTextSize, interactionPalette: _interactionPalette, ...headerWithoutPreference } = theme.header;
24599
- const nextHeader = {
24600
- ...headerWithoutPreference,
24601
- ...preferences.typography === "inherit" ? {} : { navFontRole: preferences.typography },
24602
- ...preferences.fontSize === "inherit" ? {} : { navTextSize: preferences.fontSize },
24603
- ...preferences.interactionPalette === "recommended" ? {} : { interactionPalette: preferences.interactionPalette }
24604
- };
24605
- return {
24606
- ...theme,
24607
- header: nextHeader
24608
- };
24609
- }
24610
- /**
24611
- * Applies the user's nav font-role/size overrides on top of a style-resolved
24612
- * nav link style. This is where the precedence contract lands:
24613
- *
24614
- * - The nav typography TREATMENT (case/tracking identity: plain / heading /
24615
- * quiet-caps / brand-caps) is ALWAYS owned by the active header style and is
24616
- * baked into `style` before this call (see `applyNavTypography` in
24617
- * headerLayoutStyles.ts).
24618
- * - Each treatment resolves through a DEFAULT font role: `heading` resolves
24619
- * through the heading role; `plain` / `quiet-caps` / `brand-caps` resolve
24620
- * through the body role.
24621
- * - The user's `navFontRole` (surfaced here as `preferences.typography`) only
24622
- * overrides which font ROLE the treatment resolves through. It never changes
24623
- * the treatment. `'inherit'` keeps the treatment's default role.
24624
- */
24625
- function mergeHeaderNavTypographyPreference(style, preferences) {
24626
- return preferences.typography === "inherit" && preferences.fontSize === "inherit" ? style : {
24627
- ...style,
24628
- ...preferences.typography === "inherit" ? {} : { typography: preferences.typography },
24629
- ...preferences.fontSize === "inherit" ? {} : { textSize: preferences.fontSize }
24630
- };
24631
- }
24632
- function readHeaderNavFontSizePreference(value) {
24633
- return value === "sm" || value === "base" || value === "lg" || value === "xl" ? value : "inherit";
24634
- }
24635
-
24636
25965
  //#endregion
24637
25966
  //#region ../theme-core/src/site-styles/tokenRecipes.ts
24638
25967
  const themeTokenRecipeOptions = {
@@ -24748,24 +26077,24 @@ function normalizeHex$1(value) {
24748
26077
  const blue = triplet.charAt(2);
24749
26078
  return `#${red}${red}${green}${green}${blue}${blue}`;
24750
26079
  }
24751
- function collectDifferences(before, after, path$1, differences) {
26080
+ function collectDifferences(before, after, path$2, differences) {
24752
26081
  if (Object.is(before, after)) return;
24753
26082
  if (Array.isArray(before) && Array.isArray(after)) {
24754
26083
  const sharedLength = Math.min(before.length, after.length);
24755
26084
  for (let index = 0; index < sharedLength; index += 1) {
24756
- collectDifferences(before[index], after[index], `${path$1}[${index}]`, differences);
26085
+ collectDifferences(before[index], after[index], `${path$2}[${index}]`, differences);
24757
26086
  }
24758
26087
  for (let index = sharedLength; index < before.length; index += 1) {
24759
26088
  differences.push({
24760
26089
  kind: "missing",
24761
- path: `${path$1}[${index}]`,
26090
+ path: `${path$2}[${index}]`,
24762
26091
  before: before[index]
24763
26092
  });
24764
26093
  }
24765
26094
  for (let index = sharedLength; index < after.length; index += 1) {
24766
26095
  differences.push({
24767
26096
  kind: "extra",
24768
- path: `${path$1}[${index}]`,
26097
+ path: `${path$2}[${index}]`,
24769
26098
  after: after[index]
24770
26099
  });
24771
26100
  }
@@ -24776,7 +26105,7 @@ function collectDifferences(before, after, path$1, differences) {
24776
26105
  const afterKeys = new Set(Object.keys(after));
24777
26106
  const keys = [...new Set([...beforeKeys, ...afterKeys])].sort();
24778
26107
  for (const key of keys) {
24779
- const childPath = `${path$1}.${key}`;
26108
+ const childPath = `${path$2}.${key}`;
24780
26109
  if (!afterKeys.has(key)) {
24781
26110
  differences.push({
24782
26111
  kind: "missing",
@@ -24799,7 +26128,7 @@ function collectDifferences(before, after, path$1, differences) {
24799
26128
  }
24800
26129
  differences.push({
24801
26130
  kind: "changed",
24802
- path: path$1,
26131
+ path: path$2,
24803
26132
  before,
24804
26133
  after
24805
26134
  });
@@ -24970,8 +26299,8 @@ const sdkConfigureBookingVerticalResponseSchema = sdkCommandApplicationResultSch
24970
26299
  function isRecord$8(value) {
24971
26300
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
24972
26301
  }
24973
- function pullSchemaForRequest(path$1) {
24974
- switch (path$1) {
26302
+ function pullSchemaForRequest(path$2) {
26303
+ switch (path$2) {
24975
26304
  case "/pull/entries/all": return sdkPullEntriesAllResponseSchema;
24976
26305
  case "/pull/entries": return sdkPullEntriesResponseSchema;
24977
26306
  case "/pull/pages": return sdkPullPagesResponseSchema;
@@ -24983,12 +26312,12 @@ function pullSchemaForRequest(path$1) {
24983
26312
  case "/pull/venues": return sdkPullVenuesResponseSchema;
24984
26313
  case "/pull/event-categories": return sdkPullEventCategoriesResponseSchema;
24985
26314
  case "/pull/events": return sdkPullEventsResponseSchema;
24986
- default: return /^\/pull\/entries\/[^/]+$/.test(path$1) ? sdkPullEntriesByTypeResponseSchema : undefined;
26315
+ default: return /^\/pull\/entries\/[^/]+$/.test(path$2) ? sdkPullEntriesByTypeResponseSchema : undefined;
24987
26316
  }
24988
26317
  }
24989
- function rootMutationSchema(method, path$1, body) {
26318
+ function rootMutationSchema(method, path$2, body) {
24990
26319
  if (method === "POST") {
24991
- switch (path$1) {
26320
+ switch (path$2) {
24992
26321
  case "/entries": return isRecord$8(body) && body.publication === "publish" ? sdkUpsertEntryPublishResponseSchema : sdkUpsertEntryDraftResponseSchema;
24993
26322
  case "/pages": return isRecord$8(body) && body.publication === "publish" ? sdkUpsertPagePublishResponseSchema : sdkUpsertPageDraftResponseSchema;
24994
26323
  case "/navigation": return sdkUpsertNavigationMenuResponseSchema;
@@ -25001,11 +26330,11 @@ function rootMutationSchema(method, path$1, body) {
25001
26330
  case "/footer": return sdkUpsertFooterResponseSchema;
25002
26331
  }
25003
26332
  }
25004
- if (method === "PATCH" && path$1 === "/settings") return sdkUpsertSettingsResponseSchema;
25005
- if (method === "POST" && path$1 === "/settings/booking/vertical") return sdkConfigureBookingVerticalResponseSchema;
26333
+ if (method === "PATCH" && path$2 === "/settings") return sdkUpsertSettingsResponseSchema;
26334
+ if (method === "POST" && path$2 === "/settings/booking/vertical") return sdkConfigureBookingVerticalResponseSchema;
25006
26335
  return undefined;
25007
26336
  }
25008
- function isDeleteMutationPath(path$1) {
26337
+ function isDeleteMutationPath(path$2) {
25009
26338
  return [
25010
26339
  /^\/entries\/[^/]+\/[^/]+$/,
25011
26340
  /^\/pages\/[^/]+$/,
@@ -25015,35 +26344,42 @@ function isDeleteMutationPath(path$1) {
25015
26344
  /^\/venues\/[^/]+$/,
25016
26345
  /^\/event-categories\/[^/]+$/,
25017
26346
  /^\/events\/[^/]+$/
25018
- ].some((pattern) => pattern.test(path$1));
26347
+ ].some((pattern) => pattern.test(path$2));
25019
26348
  }
25020
- function nestedMutationSchema(method, path$1) {
25021
- if (method === "POST" && /^\/pages\/[^/]+\/blocks$/.test(path$1)) return sdkUpsertBlockResponseSchema;
25022
- if (method === "POST" && /^\/pages\/[^/]+\/blocks\/reorder$/.test(path$1)) return sdkReorderResponseSchema;
25023
- if (method === "PATCH" && /^\/pages\/[^/]+\/blocks\/[^/]+$/.test(path$1)) return sdkPatchPageBlockResponseSchema;
25024
- if (method === "POST" && /^\/entries\/[^/]+\/[^/]+\/(publish|unpublish)$/.test(path$1)) {
26349
+ function nestedMutationSchema(method, path$2) {
26350
+ if (method === "POST" && /^\/pages\/[^/]+\/blocks$/.test(path$2)) return sdkUpsertBlockResponseSchema;
26351
+ if (method === "POST" && /^\/pages\/[^/]+\/blocks\/reorder$/.test(path$2)) return sdkReorderResponseSchema;
26352
+ if (method === "PATCH" && /^\/pages\/[^/]+\/blocks\/[^/]+$/.test(path$2)) return sdkPatchPageBlockResponseSchema;
26353
+ if (method === "POST" && /^\/entries\/[^/]+\/[^/]+\/(publish|unpublish)$/.test(path$2)) {
25025
26354
  return sdkCommandApplicationResultSchema(sdkUpsertEntryDraftResponseSchema.shape.value);
25026
26355
  }
25027
- if (method === "POST" && /^\/pages\/[^/]+\/(publish|unpublish)$/.test(path$1)) {
26356
+ if (method === "POST" && /^\/pages\/[^/]+\/(publish|unpublish)$/.test(path$2)) {
25028
26357
  return sdkCommandApplicationResultSchema(sdkUpsertPageDraftResponseSchema.shape.value);
25029
26358
  }
25030
- if (method === "DELETE" && isDeleteMutationPath(path$1)) return sdkDeleteResponseSchema;
26359
+ if (method === "DELETE" && isDeleteMutationPath(path$2)) return sdkDeleteResponseSchema;
25031
26360
  return undefined;
25032
26361
  }
25033
- function occurrenceResponseSchema(method, path$1) {
26362
+ function occurrenceResponseSchema(method, path$2) {
25034
26363
  const collection = /^\/events\/[^/]+\/runs\/[^/]+\/occurrences$/;
25035
26364
  const member = /^\/events\/[^/]+\/runs\/[^/]+\/occurrences\/[^/]+$/;
25036
- if (method === "GET" && collection.test(path$1)) return sdkListEventOccurrencesResponseSchema;
25037
- if (method === "POST" && collection.test(path$1)) return sdkEventOccurrenceResponseSchema;
25038
- if (method === "PATCH" && member.test(path$1)) return sdkEventOccurrenceResponseSchema;
25039
- if (method === "DELETE" && member.test(path$1)) return sdkDeleteEventOccurrenceResponseSchema;
25040
- return method === "POST" && /^\/events\/[^/]+\/runs\/[^/]+\/occurrences\/[^/]+\/cancel$/.test(path$1) ? sdkEventOccurrenceResponseSchema : undefined;
26365
+ if (method === "GET" && collection.test(path$2)) return sdkListEventOccurrencesResponseSchema;
26366
+ if (method === "POST" && collection.test(path$2)) return sdkEventOccurrenceResponseSchema;
26367
+ if (method === "PATCH" && member.test(path$2)) return sdkEventOccurrenceResponseSchema;
26368
+ if (method === "DELETE" && member.test(path$2)) return sdkDeleteEventOccurrenceResponseSchema;
26369
+ return method === "POST" && /^\/events\/[^/]+\/runs\/[^/]+\/occurrences\/[^/]+\/cancel$/.test(path$2) ? sdkEventOccurrenceResponseSchema : undefined;
26370
+ }
26371
+ function mediaResponseSchema(method, path$2) {
26372
+ if (method === "GET" && path$2 === "/media") return sdkMediaListResponseSchema;
26373
+ if (method === "GET" && /^\/media\/[^/]+\/download$/.test(path$2)) return sdkMediaDownloadResponseSchema;
26374
+ if (method === "POST" && /^\/media\/[^/]+\/delete-preview$/.test(path$2)) return sdkMediaDeletePreviewResponseSchema;
26375
+ if (method === "DELETE" && /^\/media\/[^/]+$/.test(path$2)) return sdkMediaDeleteApplyResponseSchema;
26376
+ return undefined;
25041
26377
  }
25042
- function schemaForManagementRequest(method, path$1, body) {
26378
+ function schemaForManagementRequest(method, path$2, body) {
25043
26379
  if (method === "GET") {
25044
- return pullSchemaForRequest(path$1) ?? occurrenceResponseSchema(method, path$1);
26380
+ return pullSchemaForRequest(path$2) ?? occurrenceResponseSchema(method, path$2) ?? mediaResponseSchema(method, path$2);
25045
26381
  }
25046
- return rootMutationSchema(method, path$1, body) ?? occurrenceResponseSchema(method, path$1) ?? nestedMutationSchema(method, path$1);
26382
+ return rootMutationSchema(method, path$2, body) ?? occurrenceResponseSchema(method, path$2) ?? mediaResponseSchema(method, path$2) ?? nestedMutationSchema(method, path$2);
25047
26383
  }
25048
26384
  function validateManagementResponse(value, schema) {
25049
26385
  const parsed = schema.safeParse(value);
@@ -25198,10 +26534,10 @@ async function readApiResponse(response, requestInfo, options = {}) {
25198
26534
  }
25199
26535
  return envelope;
25200
26536
  }
25201
- function requestUrl(baseUrl, path$1, params) {
25202
- if (!params || Object.keys(params).length === 0) return `${baseUrl}${path$1}`;
26537
+ function requestUrl(baseUrl, path$2, params) {
26538
+ if (!params || Object.keys(params).length === 0) return `${baseUrl}${path$2}`;
25203
26539
  const query = new URLSearchParams(params).toString();
25204
- return `${baseUrl}${path$1}?${query}`;
26540
+ return `${baseUrl}${path$2}?${query}`;
25205
26541
  }
25206
26542
  function requestFetchOptions(config, method, body) {
25207
26543
  const options = {
@@ -25215,10 +26551,10 @@ function requestFetchOptions(config, method, body) {
25215
26551
  if (body !== undefined && method !== "GET") options.body = JSON.stringify(body);
25216
26552
  return options;
25217
26553
  }
25218
- async function fetchManagementResponse(config, method, path$1, url, baseUrl, options) {
26554
+ async function fetchManagementResponse(config, method, path$2, url, baseUrl, options) {
25219
26555
  const requestInfo = buildRequestInfo({
25220
26556
  method,
25221
- path: path$1,
26557
+ path: path$2,
25222
26558
  url,
25223
26559
  baseUrl,
25224
26560
  dashboardUrl: config.dashboardUrl,
@@ -25244,12 +26580,12 @@ function throwApiError(response, json, requestInfo) {
25244
26580
  }
25245
26581
  throw new ManagementApiError(`Request failed with status ${response.status}`, "sdk:http-error", undefined, response.status, requestInfo);
25246
26582
  }
25247
- async function requestWithConfig(config, method, path$1, options) {
26583
+ async function requestWithConfig(config, method, path$2, options) {
25248
26584
  const baseUrl = `${config.dashboardUrl}/api/sdk/${config.siteId}`;
25249
- const url = requestUrl(baseUrl, path$1, options?.params);
26585
+ const url = requestUrl(baseUrl, path$2, options?.params);
25250
26586
  const fetchOptions = requestFetchOptions(config, method, options?.body);
25251
- const { response, requestInfo } = await fetchManagementResponse(config, method, path$1, url, baseUrl, fetchOptions);
25252
- const responseSchema = options?.responseSchema ?? schemaForManagementRequest(method, path$1, options?.body);
26587
+ const { response, requestInfo } = await fetchManagementResponse(config, method, path$2, url, baseUrl, fetchOptions);
26588
+ const responseSchema = options?.responseSchema ?? schemaForManagementRequest(method, path$2, options?.body);
25253
26589
  const json = responseSchema ? await readApiResponse(response, requestInfo, { invalidOkResponse: true }) : await readApiResponse(response, requestInfo, { allowNonJsonOk: true });
25254
26590
  if (!json) return undefined;
25255
26591
  throwApiError(response, json, requestInfo);
@@ -25257,38 +26593,38 @@ async function requestWithConfig(config, method, path$1, options) {
25257
26593
  }
25258
26594
  function createHttpClient(config) {
25259
26595
  return {
25260
- async get(path$1, params) {
25261
- return requestWithConfig(config, "GET", path$1, { params });
26596
+ async get(path$2, params) {
26597
+ return requestWithConfig(config, "GET", path$2, { params });
25262
26598
  },
25263
- async post(path$1, body) {
25264
- return requestWithConfig(config, "POST", path$1, { body });
26599
+ async post(path$2, body) {
26600
+ return requestWithConfig(config, "POST", path$2, { body });
25265
26601
  },
25266
- async patch(path$1, body) {
25267
- return requestWithConfig(config, "PATCH", path$1, { body });
26602
+ async patch(path$2, body) {
26603
+ return requestWithConfig(config, "PATCH", path$2, { body });
25268
26604
  },
25269
- async delete(path$1, body) {
25270
- return requestWithConfig(config, "DELETE", path$1, { body });
26605
+ async delete(path$2, body) {
26606
+ return requestWithConfig(config, "DELETE", path$2, { body });
25271
26607
  },
25272
- async getValidated(path$1, schema, params) {
25273
- return requestWithConfig(config, "GET", path$1, {
26608
+ async getValidated(path$2, schema, params) {
26609
+ return requestWithConfig(config, "GET", path$2, {
25274
26610
  params,
25275
26611
  responseSchema: schema
25276
26612
  });
25277
26613
  },
25278
- async postValidated(path$1, schema, body) {
25279
- return requestWithConfig(config, "POST", path$1, {
26614
+ async postValidated(path$2, schema, body) {
26615
+ return requestWithConfig(config, "POST", path$2, {
25280
26616
  body,
25281
26617
  responseSchema: schema
25282
26618
  });
25283
26619
  },
25284
- async patchValidated(path$1, schema, body) {
25285
- return requestWithConfig(config, "PATCH", path$1, {
26620
+ async patchValidated(path$2, schema, body) {
26621
+ return requestWithConfig(config, "PATCH", path$2, {
25286
26622
  body,
25287
26623
  responseSchema: schema
25288
26624
  });
25289
26625
  },
25290
- async deleteValidated(path$1, schema, body) {
25291
- return requestWithConfig(config, "DELETE", path$1, {
26626
+ async deleteValidated(path$2, schema, body) {
26627
+ return requestWithConfig(config, "DELETE", path$2, {
25292
26628
  body,
25293
26629
  responseSchema: schema
25294
26630
  });
@@ -25903,17 +27239,51 @@ function createIdentifiersOperations(http) {
25903
27239
 
25904
27240
  //#endregion
25905
27241
  //#region ../management-client/src/media.ts
27242
+ const productionMediaOperationsDeps = {
27243
+ createHttpClient,
27244
+ fetch
27245
+ };
25906
27246
  /**
25907
27247
  * Create media operations for the management client.
25908
27248
  * Uses direct-to-storage signed upload URLs for file uploads.
25909
27249
  */
25910
- function createMediaOperations(config) {
27250
+ function createMediaOperations(config, deps = productionMediaOperationsDeps) {
25911
27251
  const baseUrl = `${config.dashboardUrl}/api/sdk/${config.siteId}`;
25912
27252
  const uploadTimeout = config.timeout ?? 10 * 60 * 1e3;
25913
- const http = createHttpClient(config);
27253
+ const http = deps.createHttpClient(config);
25914
27254
  return {
27255
+ async list(options = {}) {
27256
+ const params = {};
27257
+ if (options.search !== undefined) params.search = options.search;
27258
+ if (options.limit !== undefined) params.limit = String(options.limit);
27259
+ if (options.offset !== undefined) params.offset = String(options.offset);
27260
+ return http.get("/media", params);
27261
+ },
27262
+ async download(identifier, maxBytes) {
27263
+ const path$2 = `/media/${encodeURIComponent(identifier)}/download`;
27264
+ const transfer = await http.get(path$2);
27265
+ const requestInfo = buildRequestInfo({
27266
+ method: "GET",
27267
+ path: path$2,
27268
+ url: `${baseUrl}${path$2}`,
27269
+ baseUrl,
27270
+ dashboardUrl: config.dashboardUrl,
27271
+ siteId: config.siteId
27272
+ });
27273
+ const response = await getMediaFile(transfer.transferUrl, uploadTimeout, requestInfo, deps.fetch);
27274
+ return {
27275
+ data: await readBoundedResponseBody(response, maxBytes),
27276
+ asset: transfer.asset
27277
+ };
27278
+ },
27279
+ async previewDelete(identifier) {
27280
+ return http.post(`/media/${encodeURIComponent(identifier)}/delete-preview`);
27281
+ },
27282
+ async delete(identifier) {
27283
+ return http.delete(`/media/${encodeURIComponent(identifier)}`);
27284
+ },
25915
27285
  async upload(file) {
25916
- return uploadMediaFile(config, http, baseUrl, uploadTimeout, file);
27286
+ return uploadMediaFile(config, http, baseUrl, uploadTimeout, file, deps.fetch);
25917
27287
  },
25918
27288
  async exists(storagePath) {
25919
27289
  const result = await http.post("/media/exists", { storagePath });
@@ -25928,7 +27298,48 @@ function createMediaOperations(config) {
25928
27298
  }
25929
27299
  };
25930
27300
  }
25931
- async function uploadMediaFile(config, http, baseUrl, uploadTimeout, file) {
27301
+ async function readBoundedResponseBody(response, maxBytes) {
27302
+ const declaredLength = response.headers.get("content-length");
27303
+ if (declaredLength !== null && Number(declaredLength) > maxBytes) {
27304
+ throw new ManagementApiError(`Download exceeds the ${maxBytes}-byte limit`, "sdk:download-too-large");
27305
+ }
27306
+ if (!response.body) return Buffer.alloc(0);
27307
+ const chunks = [];
27308
+ let totalBytes = 0;
27309
+ const reader = response.body.getReader();
27310
+ try {
27311
+ for (;;) {
27312
+ const chunk = await reader.read();
27313
+ if (chunk.done) break;
27314
+ totalBytes += chunk.value.byteLength;
27315
+ if (totalBytes > maxBytes) {
27316
+ await reader.cancel();
27317
+ throw new ManagementApiError(`Download exceeds the ${maxBytes}-byte limit`, "sdk:download-too-large");
27318
+ }
27319
+ chunks.push(chunk.value);
27320
+ }
27321
+ } finally {
27322
+ reader.releaseLock();
27323
+ }
27324
+ return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)), totalBytes);
27325
+ }
27326
+ async function getMediaFile(transferUrl, timeout, requestInfo, fetchImpl) {
27327
+ try {
27328
+ const response = await fetchImpl(transferUrl, {
27329
+ method: "GET",
27330
+ signal: AbortSignal.timeout(timeout)
27331
+ });
27332
+ if (!response.ok) {
27333
+ throw new ManagementApiError(`Download failed with status ${response.status}`, "sdk:http-error", undefined, response.status, requestInfo);
27334
+ }
27335
+ return response;
27336
+ } catch (error) {
27337
+ if (error instanceof ManagementApiError) throw error;
27338
+ const timedOut = error instanceof Error && error.name === "TimeoutError";
27339
+ throw new ManagementApiError(timedOut ? `Download timed out after ${timeout}ms` : error instanceof Error ? error.message : "Network request failed", timedOut ? "sdk:timeout" : "sdk:network-error", undefined, undefined, requestInfo);
27340
+ }
27341
+ }
27342
+ async function uploadMediaFile(config, http, baseUrl, uploadTimeout, file, fetchImpl) {
25932
27343
  const filename = file.filename || "upload";
25933
27344
  const mimeType = file.contentType || "application/octet-stream";
25934
27345
  const { signedUrl, storagePath, bucket } = await http.post("/media/upload-url", {
@@ -25946,7 +27357,7 @@ async function uploadMediaFile(config, http, baseUrl, uploadTimeout, file) {
25946
27357
  dashboardUrl: config.dashboardUrl,
25947
27358
  siteId: config.siteId
25948
27359
  });
25949
- const response = await putMediaFile(signedUrl, file.data, mimeType, uploadTimeout, requestInfo);
27360
+ const response = await putMediaFile(signedUrl, file.data, mimeType, uploadTimeout, requestInfo, fetchImpl);
25950
27361
  if (!response.ok && response.status !== 409) {
25951
27362
  throw new ManagementApiError(`Upload failed with status ${response.status}`, "sdk:http-error", undefined, response.status, requestInfo);
25952
27363
  }
@@ -25960,9 +27371,9 @@ async function uploadMediaFile(config, http, baseUrl, uploadTimeout, file) {
25960
27371
  identifier: file.identifier
25961
27372
  });
25962
27373
  }
25963
- async function putMediaFile(signedUrl, data, mimeType, timeout, requestInfo) {
27374
+ async function putMediaFile(signedUrl, data, mimeType, timeout, requestInfo, fetchImpl) {
25964
27375
  try {
25965
- return await fetch(signedUrl, {
27376
+ return await fetchImpl(signedUrl, {
25966
27377
  method: "PUT",
25967
27378
  headers: { "Content-Type": mimeType },
25968
27379
  body: new Uint8Array(data),
@@ -26705,8 +28116,8 @@ async function withRiverbankEnv(target, run) {
26705
28116
  }
26706
28117
  const previous = envScopeQueue;
26707
28118
  let release;
26708
- envScopeQueue = new Promise((resolve$1) => {
26709
- release = resolve$1;
28119
+ envScopeQueue = new Promise((resolve$2) => {
28120
+ release = resolve$2;
26710
28121
  });
26711
28122
  await previous;
26712
28123
  const previousEnv = process.env.RIVERBANK_ENV;
@@ -26888,7 +28299,7 @@ function shouldConfirm(plan, prepared) {
26888
28299
  return readyTargets(prepared).length > 0 && !hasPreparationBarrierFailure(plan, prepared);
26889
28300
  }
26890
28301
  function hasPreparationBarrierFailure(plan, prepared) {
26891
- return plan.targets.length > 1 && plan.policy.confirmation === "command" && prepared.some((entry) => entry.kind === "failed");
28302
+ return plan.targets.length > 1 && (plan.policy.confirmation === "command" || plan.policy.preparationBarrier === "all-target") && prepared.some((entry) => entry.kind === "failed");
26892
28303
  }
26893
28304
  async function confirmPrepared(plan, prepared, deps) {
26894
28305
  if (!shouldConfirm(plan, prepared)) return true;
@@ -27196,7 +28607,7 @@ function formatDateShort(isoDate) {
27196
28607
  */
27197
28608
  async function parseJsonData(options) {
27198
28609
  if (options.file) {
27199
- const filePath = path.resolve(options.file);
28610
+ const filePath = path$1.resolve(options.file);
27200
28611
  const content = await fs.readFile(filePath, "utf-8");
27201
28612
  return JSON.parse(content);
27202
28613
  }
@@ -27250,12 +28661,12 @@ async function confirmAction(message, options, isRemote) {
27250
28661
  input: process.stdin,
27251
28662
  output: process.stderr
27252
28663
  });
27253
- return new Promise((resolve$1) => {
28664
+ return new Promise((resolve$2) => {
27254
28665
  let settled = false;
27255
28666
  const finish = (confirmed) => {
27256
28667
  if (settled) return;
27257
28668
  settled = true;
27258
- resolve$1(confirmed);
28669
+ resolve$2(confirmed);
27259
28670
  };
27260
28671
  rl.once("close", () => finish(false));
27261
28672
  rl.question(`${message} (y/N): `, (answer) => {
@@ -27708,7 +29119,7 @@ const resourceKinds = [
27708
29119
  "customer-pass",
27709
29120
  "customer-membership",
27710
29121
  "appointment",
27711
- "course-enrollment",
29122
+ "event-series-registration",
27712
29123
  "event-registration",
27713
29124
  "pass-redemption",
27714
29125
  "order",
@@ -27857,7 +29268,7 @@ const appointmentBookingStatuses = [
27857
29268
  "cancelled",
27858
29269
  "no_show"
27859
29270
  ];
27860
- const courseEnrollmentStatuses = ["enrolled"];
29271
+ const eventSeriesRegistrationStatuses = ["enrolled"];
27861
29272
  const eventRegistrationStatuses = ["confirmed"];
27862
29273
 
27863
29274
  //#endregion
@@ -28011,7 +29422,6 @@ const classicProfessionalTheme = {
28011
29422
  positioning: "static",
28012
29423
  shrinkOnScroll: false,
28013
29424
  maxWidth: "container",
28014
- logoOverride: null,
28015
29425
  background: {
28016
29426
  type: "color",
28017
29427
  color: "surface"
@@ -28168,7 +29578,6 @@ const vibrantCreativeTheme = {
28168
29578
  positioning: "sticky",
28169
29579
  shrinkOnScroll: false,
28170
29580
  maxWidth: "container",
28171
- logoOverride: null,
28172
29581
  background: {
28173
29582
  type: "color",
28174
29583
  color: "surface"
@@ -30380,19 +31789,19 @@ var require_react_development = /* @__PURE__ */ __commonJSMin(((exports, module)
30380
31789
  prevActScopeDepth !== actScopeDepth - 1 && console.error("You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. ");
30381
31790
  actScopeDepth = prevActScopeDepth;
30382
31791
  }
30383
- function recursivelyFlushAsyncActWork(returnValue, resolve$1, reject) {
31792
+ function recursivelyFlushAsyncActWork(returnValue, resolve$2, reject) {
30384
31793
  var queue = ReactSharedInternals$1.actQueue;
30385
31794
  if (null !== queue) if (0 !== queue.length) try {
30386
31795
  flushActQueue(queue);
30387
31796
  enqueueTask(function() {
30388
- return recursivelyFlushAsyncActWork(returnValue, resolve$1, reject);
31797
+ return recursivelyFlushAsyncActWork(returnValue, resolve$2, reject);
30389
31798
  });
30390
31799
  return;
30391
31800
  } catch (error) {
30392
31801
  ReactSharedInternals$1.thrownErrors.push(error);
30393
31802
  }
30394
31803
  else ReactSharedInternals$1.actQueue = null;
30395
- 0 < ReactSharedInternals$1.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals$1.thrownErrors), ReactSharedInternals$1.thrownErrors.length = 0, reject(queue)) : resolve$1(returnValue);
31804
+ 0 < ReactSharedInternals$1.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals$1.thrownErrors), ReactSharedInternals$1.thrownErrors.length = 0, reject(queue)) : resolve$2(returnValue);
30396
31805
  }
30397
31806
  function flushActQueue(queue) {
30398
31807
  if (!isFlushing) {
@@ -30553,14 +31962,14 @@ var require_react_development = /* @__PURE__ */ __commonJSMin(((exports, module)
30553
31962
  queueSeveralMicrotasks(function() {
30554
31963
  didAwaitActCall || didWarnNoAwaitAct || (didWarnNoAwaitAct = !0, console.error("You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"));
30555
31964
  });
30556
- return { then: function(resolve$1, reject) {
31965
+ return { then: function(resolve$2, reject) {
30557
31966
  didAwaitActCall = !0;
30558
31967
  thenable.then(function(returnValue) {
30559
31968
  popActScope(prevActQueue, prevActScopeDepth);
30560
31969
  if (0 === prevActScopeDepth) {
30561
31970
  try {
30562
31971
  flushActQueue(queue), enqueueTask(function() {
30563
- return recursivelyFlushAsyncActWork(returnValue, resolve$1, reject);
31972
+ return recursivelyFlushAsyncActWork(returnValue, resolve$2, reject);
30564
31973
  });
30565
31974
  } catch (error$0) {
30566
31975
  ReactSharedInternals$1.thrownErrors.push(error$0);
@@ -30570,7 +31979,7 @@ var require_react_development = /* @__PURE__ */ __commonJSMin(((exports, module)
30570
31979
  ReactSharedInternals$1.thrownErrors.length = 0;
30571
31980
  reject(_thrownError);
30572
31981
  }
30573
- } else resolve$1(returnValue);
31982
+ } else resolve$2(returnValue);
30574
31983
  }, function(error) {
30575
31984
  popActScope(prevActQueue, prevActScopeDepth);
30576
31985
  0 < ReactSharedInternals$1.thrownErrors.length ? (error = aggregateErrors(ReactSharedInternals$1.thrownErrors), ReactSharedInternals$1.thrownErrors.length = 0, reject(error)) : reject(error);
@@ -30583,11 +31992,11 @@ var require_react_development = /* @__PURE__ */ __commonJSMin(((exports, module)
30583
31992
  didAwaitActCall || didWarnNoAwaitAct || (didWarnNoAwaitAct = !0, console.error("A component suspended inside an `act` scope, but the `act` call was not awaited. When testing React components that depend on asynchronous data, you must await the result:\n\nawait act(() => ...)"));
30584
31993
  }), ReactSharedInternals$1.actQueue = null);
30585
31994
  if (0 < ReactSharedInternals$1.thrownErrors.length) throw callback = aggregateErrors(ReactSharedInternals$1.thrownErrors), ReactSharedInternals$1.thrownErrors.length = 0, callback;
30586
- return { then: function(resolve$1, reject) {
31995
+ return { then: function(resolve$2, reject) {
30587
31996
  didAwaitActCall = !0;
30588
31997
  0 === prevActScopeDepth ? (ReactSharedInternals$1.actQueue = queue, enqueueTask(function() {
30589
- return recursivelyFlushAsyncActWork(returnValue$jscomp$0, resolve$1, reject);
30590
- })) : resolve$1(returnValue$jscomp$0);
31998
+ return recursivelyFlushAsyncActWork(returnValue$jscomp$0, resolve$2, reject);
31999
+ })) : resolve$2(returnValue$jscomp$0);
30591
32000
  } };
30592
32001
  };
30593
32002
  exports.cache = function(fn) {
@@ -31363,7 +32772,7 @@ const appointmentBookingRefSchema = resourceRefSchema("appointment");
31363
32772
  const appointmentResourceRefSchema = resourceRefSchema("appointment-resource");
31364
32773
  const appointmentServiceRefSchema = resourceRefSchema("appointment-service");
31365
32774
  const bookingCustomerRefSchema = resourceRefSchema("customer");
31366
- const courseEnrollmentRefSchema = resourceRefSchema("course-enrollment");
32775
+ const eventSeriesRegistrationRefSchema = resourceRefSchema("event-series-registration");
31367
32776
  const customerMembershipRefSchema = resourceRefSchema("customer-membership");
31368
32777
  const customerPassRefSchema = resourceRefSchema("customer-pass");
31369
32778
  const eventRegistrationRefSchema = resourceRefSchema("event-registration");
@@ -31544,13 +32953,13 @@ const seedAppointmentBookingCommandSchema = z.object({
31544
32953
  });
31545
32954
  })
31546
32955
  }).strict();
31547
- const seedCourseEnrollmentCommandSchema = z.object({
31548
- type: z.literal("seedCourseEnrollment"),
31549
- ref: courseEnrollmentRefSchema,
32956
+ const seedEventSeriesRegistrationCommandSchema = z.object({
32957
+ type: z.literal("seedEventSeriesRegistration"),
32958
+ ref: eventSeriesRegistrationRefSchema,
31550
32959
  input: z.object({
31551
32960
  customerRef: bookingCustomerRefSchema,
31552
32961
  eventRef: eventSeriesRefSchema$1,
31553
- status: z.enum(courseEnrollmentStatuses).default("enrolled"),
32962
+ status: z.enum(eventSeriesRegistrationStatuses).default("enrolled"),
31554
32963
  paymentMethod: z.literal("free").default("free"),
31555
32964
  paymentStatus: z.literal("not_required").default("not_required"),
31556
32965
  formSubmission: unknownRecordSchema.default({}),
@@ -31592,8 +33001,8 @@ const portalOrderSubjectSchema = z.discriminatedUnion("kind", [z.object({
31592
33001
  kind: z.literal("event_registration"),
31593
33002
  eventRegistrationRef: eventRegistrationRefSchema
31594
33003
  }).strict(), z.object({
31595
- kind: z.literal("course_enrollment"),
31596
- courseEnrollmentRef: courseEnrollmentRefSchema
33004
+ kind: z.literal("event_series_registration"),
33005
+ eventSeriesRegistrationRef: eventSeriesRegistrationRefSchema
31597
33006
  }).strict()]);
31598
33007
  const seedPortalOrderCommandSchema = z.object({
31599
33008
  type: z.literal("seedPortalOrder"),
@@ -32253,7 +33662,7 @@ const siteCommandSchema = z.discriminatedUnion("type", [
32253
33662
  grantCustomerPassCommandSchema,
32254
33663
  grantCustomerMembershipCommandSchema,
32255
33664
  seedAppointmentBookingCommandSchema,
32256
- seedCourseEnrollmentCommandSchema,
33665
+ seedEventSeriesRegistrationCommandSchema,
32257
33666
  seedEventRegistrationCommandSchema,
32258
33667
  seedPassRedemptionCommandSchema,
32259
33668
  seedPortalOrderCommandSchema,
@@ -32377,7 +33786,7 @@ const commandScopes = [
32377
33786
  "customer_pass",
32378
33787
  "customer_membership",
32379
33788
  "appointment",
32380
- "course_enrollment",
33789
+ "event_series_registration",
32381
33790
  "event_registration",
32382
33791
  "pass_redemption",
32383
33792
  "order",
@@ -32972,8 +34381,8 @@ const siteCommandMetadata = {
32972
34381
  capability: "bookings.appointments.write",
32973
34382
  support: "supported"
32974
34383
  },
32975
- seedCourseEnrollment: {
32976
- scope: "course_enrollment",
34384
+ seedEventSeriesRegistration: {
34385
+ scope: "event_series_registration",
32977
34386
  risk: "medium",
32978
34387
  writeSemantics: "upsert",
32979
34388
  lifecycleIntent: "seed",
@@ -32982,7 +34391,7 @@ const siteCommandMetadata = {
32982
34391
  previewability: "dry_run_only",
32983
34392
  allowedRuntimes: allRuntimes,
32984
34393
  intendedExposure: recipeExposure,
32985
- capability: "bookings.course_enrollments.write",
34394
+ capability: "bookings.event_series_registrations.write",
32986
34395
  support: "supported"
32987
34396
  },
32988
34397
  seedEventRegistration: {
@@ -33199,7 +34608,7 @@ const bookingRefCommandTypeValues = [
33199
34608
  "grantCustomerPass",
33200
34609
  "grantCustomerMembership",
33201
34610
  "seedAppointmentBooking",
33202
- "seedCourseEnrollment",
34611
+ "seedEventSeriesRegistration",
33203
34612
  "seedEventRegistration",
33204
34613
  "seedPassRedemption",
33205
34614
  "seedPortalOrder",
@@ -33253,7 +34662,7 @@ function bookingCommandRefContributions(command) {
33253
34662
  field: "input.resourceRef"
33254
34663
  }
33255
34664
  ]);
33256
- case "seedCourseEnrollment": return providedWithRequirements(command.ref, [{
34665
+ case "seedEventSeriesRegistration": return providedWithRequirements(command.ref, [{
33257
34666
  ref: command.input.customerRef,
33258
34667
  field: "input.customerRef"
33259
34668
  }, {
@@ -33329,9 +34738,9 @@ function portalOrderSubjectRefContribution(subject) {
33329
34738
  ref: subject.eventRegistrationRef,
33330
34739
  field: "input.subject.eventRegistrationRef"
33331
34740
  };
33332
- case "course_enrollment": return {
33333
- ref: subject.courseEnrollmentRef,
33334
- field: "input.subject.courseEnrollmentRef"
34741
+ case "event_series_registration": return {
34742
+ ref: subject.eventSeriesRegistrationRef,
34743
+ field: "input.subject.eventSeriesRegistrationRef"
33335
34744
  };
33336
34745
  default: return assertNever(subject);
33337
34746
  }
@@ -33533,13 +34942,13 @@ function optionalRef(ref, field) {
33533
34942
  field
33534
34943
  }] : [];
33535
34944
  }
33536
- function collectVenueRefsFromIntervals(intervals, path$1) {
33537
- return intervals.flatMap((interval, index) => optionalRef(interval.venueRef, `${path$1}[${index}].venueRef`));
34945
+ function collectVenueRefsFromIntervals(intervals, path$2) {
34946
+ return intervals.flatMap((interval, index) => optionalRef(interval.venueRef, `${path$2}[${index}].venueRef`));
33538
34947
  }
33539
- function collectNavigationRefs(items, path$1) {
34948
+ function collectNavigationRefs(items, path$2) {
33540
34949
  return items.flatMap((item, index) => {
33541
34950
  if (!isRecord$5(item)) return [];
33542
- const itemPath = `${path$1}[${index}]`;
34951
+ const itemPath = `${path$2}[${index}]`;
33543
34952
  const refs = [];
33544
34953
  if (typeof item.pageRef === "string") {
33545
34954
  refs.push({
@@ -33559,26 +34968,26 @@ function collectNavigationRefs(items, path$1) {
33559
34968
  return refs;
33560
34969
  });
33561
34970
  }
33562
- function collectBlockRefs(blocks, path$1) {
34971
+ function collectBlockRefs(blocks, path$2) {
33563
34972
  return blocks.flatMap((block, index) => {
33564
34973
  if (!isRecord$5(block)) return [];
33565
- const blockPath = `${path$1}[${index}]`;
34974
+ const blockPath = `${path$2}[${index}]`;
33566
34975
  return [...collectNestedRefs(block.props, `${blockPath}.props`), ...collectNestedRefs(block.children, `${blockPath}.children`)];
33567
34976
  });
33568
34977
  }
33569
- function collectNestedRefs(value, path$1) {
34978
+ function collectNestedRefs(value, path$2) {
33570
34979
  if (typeof value === "string") {
33571
34980
  const parsed = parseResourceRef(value);
33572
34981
  return parsed.ok ? [{
33573
34982
  ref: value,
33574
- field: path$1
34983
+ field: path$2
33575
34984
  }] : [];
33576
34985
  }
33577
34986
  if (Array.isArray(value)) {
33578
- return value.flatMap((item, index) => collectNestedRefs(item, `${path$1}[${index}]`));
34987
+ return value.flatMap((item, index) => collectNestedRefs(item, `${path$2}[${index}]`));
33579
34988
  }
33580
34989
  if (!isRecord$5(value)) return [];
33581
- return Object.entries(value).flatMap(([key, child]) => collectNestedRefs(child, `${path$1}.${key}`));
34990
+ return Object.entries(value).flatMap(([key, child]) => collectNestedRefs(child, `${path$2}.${key}`));
33582
34991
  }
33583
34992
 
33584
34993
  //#endregion
@@ -33765,7 +35174,7 @@ function matchSiteCommand(command, match) {
33765
35174
  case "grantCustomerPass": return match(command);
33766
35175
  case "grantCustomerMembership": return match(command);
33767
35176
  case "seedAppointmentBooking": return match(command);
33768
- case "seedCourseEnrollment": return match(command);
35177
+ case "seedEventSeriesRegistration": return match(command);
33769
35178
  case "seedEventRegistration": return match(command);
33770
35179
  case "seedPassRedemption": return match(command);
33771
35180
  case "seedPortalOrder": return match(command);
@@ -33844,8 +35253,8 @@ function invalidPayloadError(input, issue) {
33844
35253
  message: `${field}: ${issue.message}`
33845
35254
  };
33846
35255
  }
33847
- function commandIndexForIssuePath(path$1) {
33848
- return path$1[0] === "commands" && typeof path$1[1] === "number" ? path$1[1] : -1;
35256
+ function commandIndexForIssuePath(path$2) {
35257
+ return path$2[0] === "commands" && typeof path$2[1] === "number" ? path$2[1] : -1;
33849
35258
  }
33850
35259
  function commandTypeForIssue(input, commandIndex) {
33851
35260
  if (!isRecord$5(input) || !isUnknownArray(input.commands)) return null;
@@ -33855,9 +35264,9 @@ function commandTypeForIssue(input, commandIndex) {
33855
35264
  function isUnknownArray(value) {
33856
35265
  return Array.isArray(value);
33857
35266
  }
33858
- function formatIssuePath(path$1) {
33859
- if (path$1.length === 0) return "<root>";
33860
- return path$1.reduce((formatted, segment) => {
35267
+ function formatIssuePath(path$2) {
35268
+ if (path$2.length === 0) return "<root>";
35269
+ return path$2.reduce((formatted, segment) => {
33861
35270
  if (typeof segment === "number") return `${formatted}[${segment}]`;
33862
35271
  if (formatted.length === 0) return String(segment);
33863
35272
  return `${formatted}.${String(segment)}`;
@@ -34022,7 +35431,7 @@ function checkPortalOrdersHaveUniqueSubjects(batch) {
34022
35431
  function portalOrderSubjectKey(subject) {
34023
35432
  switch (subject.kind) {
34024
35433
  case "event_registration": return `event_registration:${subject.eventRegistrationRef}`;
34025
- case "course_enrollment": return `course_enrollment:${subject.courseEnrollmentRef}`;
35434
+ case "event_series_registration": return `event_series_registration:${subject.eventSeriesRegistrationRef}`;
34026
35435
  }
34027
35436
  }
34028
35437
  function indexOfferingCommands(batch) {
@@ -34071,7 +35480,7 @@ function indexOfferingCommands(batch) {
34071
35480
  case "grantCustomerPass":
34072
35481
  case "grantCustomerMembership":
34073
35482
  case "seedAppointmentBooking":
34074
- case "seedCourseEnrollment":
35483
+ case "seedEventSeriesRegistration":
34075
35484
  case "seedEventRegistration":
34076
35485
  case "seedPassRedemption":
34077
35486
  case "seedPortalOrder":
@@ -34590,6 +35999,8 @@ const FILE_SIZE_LIMITS = {
34590
35999
  instagramZip: unsafeAsPositiveByteCount(100 * 1024 * 1024),
34591
36000
  bookingEvidence: unsafeAsPositiveByteCount(25 * 1024 * 1024)
34592
36001
  };
36002
+ /** Shared admission ceiling for SDK-management media uploads before intent-specific finalization. */
36003
+ const MAX_MANAGEMENT_MEDIA_UPLOAD_BYTES = unsafeAsPositiveByteCount(250 * 1024 * 1024);
34593
36004
  const DEFAULT_PRIVATE_FILE_SIGNED_URL_EXPIRY_SECONDS = unsafeAsPositiveSeconds(5 * 60);
34594
36005
  const MAX_SAFE_DOWNLOAD_FILENAME_LENGTH = 180;
34595
36006
  function asPositiveByteCount(value) {
@@ -34853,6 +36264,16 @@ function classifyKnownMimeType(mimeType) {
34853
36264
  default: return err("unsupported-mime-type");
34854
36265
  }
34855
36266
  }
36267
+ function managementMediaLibraryUploadLimitForMimeType(mimeType) {
36268
+ const kind = classifyKnownMimeType(mimeType);
36269
+ if (!kind.ok) return kind;
36270
+ const renderUse = kind.value.tag;
36271
+ if (renderUse !== "image" && renderUse !== "video" && renderUse !== "audio") {
36272
+ return err("unsupported-mime-type");
36273
+ }
36274
+ const libraryLimit = mediaLibraryMaxBytes([renderUse]);
36275
+ return ok(unsafeAsPositiveByteCount(Math.min(libraryLimit, MAX_MANAGEMENT_MEDIA_UPLOAD_BYTES)));
36276
+ }
34856
36277
  function isMimeTypeAllowedForPolicy(mimeType, policy) {
34857
36278
  const kindResult = classifyKnownMimeType(mimeType);
34858
36279
  return kindResult.ok && isFileKindAllowed(kindResult.value, policy);
@@ -35420,9 +36841,9 @@ function buildDurableFileStoragePath(input) {
35420
36841
  input.fileAssetId,
35421
36842
  input.safeDownloadFilename
35422
36843
  ];
35423
- const path$1 = joinStoragePathSegments(segments);
35424
- if (!path$1.ok) return path$1;
35425
- return ok(unsafeAsSiteScopedStoragePath(path$1.value));
36844
+ const path$2 = joinStoragePathSegments(segments);
36845
+ if (!path$2.ok) return path$2;
36846
+ return ok(unsafeAsSiteScopedStoragePath(path$2.value));
35426
36847
  }
35427
36848
  function parseStoragePath(value) {
35428
36849
  return joinStoragePathSegments(value.split("/"));
@@ -36847,10 +38268,10 @@ function emptyStatePatch(patch) {
36847
38268
  rejection_reason: patch.rejection_reason ?? null
36848
38269
  };
36849
38270
  }
36850
- function traverse(items, parse) {
38271
+ function traverse(items, parse$1) {
36851
38272
  const values = [];
36852
38273
  for (const item of items) {
36853
- const parsed = parse(item);
38274
+ const parsed = parse$1(item);
36854
38275
  if (!parsed.ok) return parsed;
36855
38276
  values.push(parsed.value);
36856
38277
  }
@@ -37531,18 +38952,18 @@ function resolveFileDownload(input) {
37531
38952
  assetKind: input.asset.kind
37532
38953
  });
37533
38954
  }
37534
- const access = authorizeFileAccess(input.actor, input.asset.accessPolicy);
37535
- if (!access.ok) {
38955
+ const access$1 = authorizeFileAccess(input.actor, input.asset.accessPolicy);
38956
+ if (!access$1.ok) {
37536
38957
  return err({
37537
38958
  tag: "access-denied",
37538
- reason: access.error
38959
+ reason: access$1.error
37539
38960
  });
37540
38961
  }
37541
38962
  return ok(fileAccessGrantToDownloadPlan({
37542
38963
  asset: input.asset,
37543
38964
  requestedUse: input.requestedUse,
37544
38965
  disposition: input.disposition ?? defaultDispositionForFileUse(input.requestedUse),
37545
- grant: access.value
38966
+ grant: access$1.value
37546
38967
  }));
37547
38968
  }
37548
38969
  function fileAccessGrantToDownloadPlan(input) {
@@ -38073,9 +39494,9 @@ const EXTENSIONS_BY_MIME_TYPE = {
38073
39494
  };
38074
39495
  function fileExtension(filename) {
38075
39496
  const pathSegments = filename.replace(/\\/g, "/").split("/");
38076
- const basename = pathSegments[pathSegments.length - 1] ?? filename;
38077
- const dotIndex = basename.lastIndexOf(".");
38078
- return dotIndex <= 0 ? "" : basename.slice(dotIndex).toLowerCase();
39497
+ const basename$1 = pathSegments[pathSegments.length - 1] ?? filename;
39498
+ const dotIndex = basename$1.lastIndexOf(".");
39499
+ return dotIndex <= 0 ? "" : basename$1.slice(dotIndex).toLowerCase();
38079
39500
  }
38080
39501
  function getMimeType(filename) {
38081
39502
  return MIME_TYPES_BY_EXTENSION[fileExtension(filename)] ?? "application/octet-stream";
@@ -38623,20 +40044,20 @@ function formatMediaUsageStage(stage) {
38623
40044
  function mediaUsageLocationLabel(location$1) {
38624
40045
  switch (location$1.kind) {
38625
40046
  case "pageBlock": {
38626
- const path$1 = location$1.pageDraftPath ?? location$1.pagePublishedPath ?? null;
40047
+ const path$2 = location$1.pageDraftPath ?? location$1.pagePublishedPath ?? null;
38627
40048
  const title = location$1.pageTitle ?? "Untitled page";
38628
- const where = path$1 ? `${title} (${path$1})` : title;
40049
+ const where = path$2 ? `${title} (${path$2})` : title;
38629
40050
  return `${where} — ${formatMediaUsageStage(location$1.stage)} — Block: ${location$1.blockKind}`;
38630
40051
  }
38631
40052
  case "entrySlotBlock": {
38632
- const path$1 = location$1.routeDraftPath ?? location$1.routePublishedPath ?? null;
38633
- const where = path$1 ? `${location$1.entryTitle} (${path$1})` : location$1.entryTitle;
40053
+ const path$2 = location$1.routeDraftPath ?? location$1.routePublishedPath ?? null;
40054
+ const where = path$2 ? `${location$1.entryTitle} (${path$2})` : location$1.entryTitle;
38634
40055
  return `${where} — Page Content: ${location$1.slotKey} — ${formatMediaUsageStage(location$1.stage)} — Block: ${location$1.blockKind}`;
38635
40056
  }
38636
40057
  case "surfaceSlotBlock": return `Page surface ${location$1.pageSurfaceId} — Page Content: ${location$1.slotKey} — ${formatMediaUsageStage(location$1.stage)} — Block: ${location$1.blockKind}`;
38637
40058
  case "contentEntry": {
38638
- const path$1 = location$1.routeDraftPath ?? location$1.routePublishedPath ?? null;
38639
- const where = path$1 ? `${location$1.entryTitle} (${path$1})` : location$1.entryTitle;
40059
+ const path$2 = location$1.routeDraftPath ?? location$1.routePublishedPath ?? null;
40060
+ const where = path$2 ? `${location$1.entryTitle} (${path$2})` : location$1.entryTitle;
38640
40061
  return `${where} — ${formatMediaUsageStage(location$1.stage)}`;
38641
40062
  }
38642
40063
  case "productFileDownload": return `${location$1.productTitle} — Product download`;
@@ -38666,10 +40087,17 @@ function hasMediaUsage(response) {
38666
40087
  return response.items.some((item) => item.usedCount > 0);
38667
40088
  }
38668
40089
  function planBulkDeleteCompletionToast(result) {
40090
+ const hasWarnings = (result.warnings?.length ?? 0) > 0;
38669
40091
  if (result.failed > 0) {
38670
40092
  return {
38671
40093
  kind: "error",
38672
- message: `Deleted ${result.deleted}. Failed to delete ${result.failed}.`
40094
+ message: hasWarnings ? `Deleted ${result.deleted}. Failed to delete ${result.failed}. Some cleanup could not be completed.` : `Deleted ${result.deleted}. Failed to delete ${result.failed}.`
40095
+ };
40096
+ }
40097
+ if (hasWarnings) {
40098
+ return {
40099
+ kind: "warning",
40100
+ message: result.deleted === 1 ? "Deleted 1 media item, but some cleanup could not be completed." : `Deleted ${result.deleted} media items, but some cleanup could not be completed.`
38673
40101
  };
38674
40102
  }
38675
40103
  return {
@@ -38934,7 +40362,7 @@ const ORDER = [
38934
40362
  ];
38935
40363
  const getImageUrl = (asset, params = {}, basePath = DEFAULT_BASE_PATH) => {
38936
40364
  const trimmedBase = basePath.endsWith("/") ? basePath.slice(0, Math.max(basePath.length - 1, 0)) : basePath;
38937
- const path$1 = `${trimmedBase}/${encodeURIComponent(asset.id)}`;
40365
+ const path$2 = `${trimmedBase}/${encodeURIComponent(asset.id)}`;
38938
40366
  const searchParams = new URLSearchParams();
38939
40367
  if (params.w !== undefined) searchParams.set("w", formatNumber(params.w));
38940
40368
  if (params.h !== undefined) searchParams.set("h", formatNumber(params.h));
@@ -38953,7 +40381,7 @@ const getImageUrl = (asset, params = {}, basePath = DEFAULT_BASE_PATH) => {
38953
40381
  if (value !== null) sorted.set(key, value);
38954
40382
  });
38955
40383
  const query = sorted.toString();
38956
- return query ? `${path$1}?${query}` : path$1;
40384
+ return query ? `${path$2}?${query}` : path$2;
38957
40385
  };
38958
40386
 
38959
40387
  //#endregion
@@ -39723,8 +41151,8 @@ function mediaIdentifierOrFallback(current, fallback) {
39723
41151
  function isRecord$4(value) {
39724
41152
  return typeof value === "object" && value !== null && !Array.isArray(value);
39725
41153
  }
39726
- function formatContentFieldPath(path$1) {
39727
- return path$1.map((segment, index) => typeof segment === "number" ? `[${segment}]` : index === 0 ? segment : `.${segment}`).join("");
41154
+ function formatContentFieldPath(path$2) {
41155
+ return path$2.map((segment, index) => typeof segment === "number" ? `[${segment}]` : index === 0 ? segment : `.${segment}`).join("");
39728
41156
  }
39729
41157
  function summarizeContentMediaValue(value) {
39730
41158
  if (typeof value === "string") {
@@ -39737,7 +41165,7 @@ function summarizeContentMediaValue(value) {
39737
41165
  if (isRecord$4(value)) return `object(${Object.keys(value).sort().join(",")})`;
39738
41166
  return typeof value;
39739
41167
  }
39740
- function auditMediaValue(context, path$1, allowedMediaTypes$1, value) {
41168
+ function auditMediaValue(context, path$2, allowedMediaTypes$1, value) {
39741
41169
  const expectedMediaType = allowedMediaTypes$1[0];
39742
41170
  if (value === undefined || value === null) return [];
39743
41171
  if (typeof value === "string") {
@@ -39746,7 +41174,7 @@ function auditMediaValue(context, path$1, allowedMediaTypes$1, value) {
39746
41174
  return [{
39747
41175
  tag: "blank-string",
39748
41176
  context,
39749
- path: path$1,
41177
+ path: path$2,
39750
41178
  valueSummary: summarizeContentMediaValue(value),
39751
41179
  expectedMediaType
39752
41180
  }];
@@ -39754,7 +41182,7 @@ function auditMediaValue(context, path$1, allowedMediaTypes$1, value) {
39754
41182
  return [{
39755
41183
  tag: "legacy-string",
39756
41184
  context,
39757
- path: path$1,
41185
+ path: path$2,
39758
41186
  valueSummary: summarizeContentMediaValue(value),
39759
41187
  originalValue: value,
39760
41188
  expectedMediaType,
@@ -39770,7 +41198,7 @@ function auditMediaValue(context, path$1, allowedMediaTypes$1, value) {
39770
41198
  return [{
39771
41199
  tag: "malformed-object",
39772
41200
  context,
39773
- path: path$1,
41201
+ path: path$2,
39774
41202
  valueSummary: summarizeContentMediaValue(value),
39775
41203
  expectedMediaType
39776
41204
  }];
@@ -40173,8 +41601,8 @@ function mediaTypeFromAsset(row) {
40173
41601
  }
40174
41602
  function legacyStorageValues(siteId, storagePath) {
40175
41603
  const sitePrefix = `sites/${siteId}/`;
40176
- const relative = storagePath.startsWith(sitePrefix) ? storagePath.slice(sitePrefix.length) : storagePath;
40177
- return [...new Set([storagePath, `/${relative}`])];
41604
+ const relative$1 = storagePath.startsWith(sitePrefix) ? storagePath.slice(sitePrefix.length) : storagePath;
41605
+ return [...new Set([storagePath, `/${relative$1}`])];
40178
41606
  }
40179
41607
  function assetCandidates(rows) {
40180
41608
  return rows.flatMap((row) => {
@@ -40562,10 +41990,10 @@ var BrowserImage = class extends ImageBase {
40562
41990
  return Promise.reject(new Error(`Cannot load buffer as an image in browser`));
40563
41991
  }
40564
41992
  this.image = img;
40565
- return new Promise((resolve$1, reject) => {
41993
+ return new Promise((resolve$2, reject) => {
40566
41994
  const onImageLoad = () => {
40567
41995
  this._initCanvas();
40568
- resolve$1(this);
41996
+ resolve$2(this);
40569
41997
  };
40570
41998
  if (img.complete) {
40571
41999
  onImageLoad();
@@ -42151,7 +43579,7 @@ const isMediaValue = (value) => isRecordValue(value) && !("attrs" in value) && t
42151
43579
  //#endregion
42152
43580
  //#region src/content/media-manifest.ts
42153
43581
  const MANIFEST_VERSION = 2;
42154
- const getManifestPath = (contentDir) => path.join(contentDir, "media", ".manifest.json");
43582
+ const getManifestPath = (contentDir) => path$1.join(contentDir, "media", ".manifest.json");
42155
43583
  const ensureDir = async (dirPath) => {
42156
43584
  try {
42157
43585
  await fs.mkdir(dirPath, { recursive: true });
@@ -42243,7 +43671,7 @@ async function readMediaManifest(contentDir) {
42243
43671
  return createEmptyMediaManifest();
42244
43672
  }
42245
43673
  async function writeMediaManifest(contentDir, manifest) {
42246
- const mediaDir = path.join(contentDir, "media");
43674
+ const mediaDir = path$1.join(contentDir, "media");
42247
43675
  await ensureDir(mediaDir);
42248
43676
  const updatedManifest = {
42249
43677
  ...manifest,
@@ -42379,7 +43807,7 @@ const DEFAULT_RETRY_CONFIG = {
42379
43807
  * Sleep for a given number of milliseconds.
42380
43808
  */
42381
43809
  function sleep(ms) {
42382
- return new Promise((resolve$1) => setTimeout(resolve$1, ms));
43810
+ return new Promise((resolve$2) => setTimeout(resolve$2, ms));
42383
43811
  }
42384
43812
  /**
42385
43813
  * Calculate exponential backoff delay with jitter.
@@ -42488,8 +43916,8 @@ const TRANSFORMABLE_EXTENSIONS = [
42488
43916
  * Check if a file path is an image that can be transformed by Supabase.
42489
43917
  * @param path - The file path or storage path
42490
43918
  */
42491
- function isTransformableImage(path$1) {
42492
- const ext = path$1.split(".").pop()?.toLowerCase();
43919
+ function isTransformableImage(path$2) {
43920
+ const ext = path$2.split(".").pop()?.toLowerCase();
42493
43921
  return TRANSFORMABLE_EXTENSIONS.includes(ext ?? "");
42494
43922
  }
42495
43923
  /**
@@ -42800,7 +44228,7 @@ function collectRichTextImageAttrs(value, out = [], location$1 = "") {
42800
44228
  //#endregion
42801
44229
  //#region src/media/pull-manifest.ts
42802
44230
  const buildLocalFilename = (identifier, asset) => {
42803
- const extensionFromFilename = path.extname(asset.filename ?? "");
44231
+ const extensionFromFilename = path$1.extname(asset.filename ?? "");
42804
44232
  const extension = extensionFromFilename || getExtensionForMimeType(asset.mimeType) || "";
42805
44233
  return extension ? `${identifier}${extension}` : identifier;
42806
44234
  };
@@ -43122,6 +44550,165 @@ function sha256(buffer) {
43122
44550
  return createHash("sha256").update(buffer).digest("hex");
43123
44551
  }
43124
44552
 
44553
+ //#endregion
44554
+ //#region src/files/stable-binary-file.ts
44555
+ function isNodeError(error) {
44556
+ return error instanceof Error && "code" in error;
44557
+ }
44558
+ function observationsMatch(left, right) {
44559
+ return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs;
44560
+ }
44561
+ function admitStableBinaryRead(stats, maxBytes) {
44562
+ if (!stats.isFile()) return err({ tag: "not_a_regular_file" });
44563
+ return stats.size > maxBytes ? err({
44564
+ tag: "file_too_large",
44565
+ maxBytes,
44566
+ actualBytes: stats.size
44567
+ }) : ok(undefined);
44568
+ }
44569
+ function mapStableBinaryReadFailure(error) {
44570
+ if (!isNodeError(error)) return { tag: "read_failed" };
44571
+ if (error.code === "ENOENT") return { tag: "file_not_found" };
44572
+ return error.code === "ELOOP" || error.code === "EISDIR" ? { tag: "not_a_regular_file" } : { tag: "read_failed" };
44573
+ }
44574
+ async function readObservedByteLength(handle, byteLength) {
44575
+ const data = Buffer.allocUnsafe(byteLength);
44576
+ let offset = 0;
44577
+ while (offset < byteLength) {
44578
+ const read = await handle.read(data, offset, byteLength - offset, offset);
44579
+ if (read.bytesRead === 0) return null;
44580
+ offset += read.bytesRead;
44581
+ }
44582
+ const growthProbe = Buffer.allocUnsafe(1);
44583
+ const extra = await handle.read(growthProbe, 0, 1, byteLength);
44584
+ return extra.bytesRead === 0 ? data : null;
44585
+ }
44586
+ async function readStableBinaryFile(filePath, maxBytes) {
44587
+ let handle;
44588
+ try {
44589
+ const noFollow = constants.O_NOFOLLOW ?? 0;
44590
+ handle = await open(filePath, constants.O_RDONLY | noFollow);
44591
+ const before = await handle.stat();
44592
+ const admission = admitStableBinaryRead(before, maxBytes);
44593
+ if (!admission.ok) return admission;
44594
+ const data = await readObservedByteLength(handle, before.size);
44595
+ const after = await handle.stat();
44596
+ if (data === null || !observationsMatch(before, after)) {
44597
+ return err({ tag: "file_changed_during_read" });
44598
+ }
44599
+ return ok(data);
44600
+ } catch (error) {
44601
+ return err(mapStableBinaryReadFailure(error));
44602
+ } finally {
44603
+ await handle?.close().catch(() => undefined);
44604
+ }
44605
+ }
44606
+ async function inspectDestination(filePath, overwrite) {
44607
+ try {
44608
+ const stats = await lstat(filePath);
44609
+ if (overwrite === "refuse") return err({ tag: "destination_exists" });
44610
+ return stats.isFile() ? ok(undefined) : err({ tag: "unsafe_destination" });
44611
+ } catch (error) {
44612
+ if (isNodeError(error) && error.code === "ENOENT") return ok(undefined);
44613
+ return err({ tag: "write_failed" });
44614
+ }
44615
+ }
44616
+ /**
44617
+ * Admit an output path before a remote operation while preserving the atomic
44618
+ * writer's second check at finalization time to close the race window.
44619
+ */
44620
+ async function admitBinaryWriteDestination(filePath, overwrite) {
44621
+ const destination = await inspectDestination(filePath, overwrite);
44622
+ if (!destination.ok) return destination;
44623
+ try {
44624
+ const parent = dirname$1(filePath);
44625
+ const parentStats = await lstat(parent);
44626
+ if (!parentStats.isDirectory()) return err({ tag: "unsafe_destination" });
44627
+ await access(parent, constants.W_OK);
44628
+ return ok(undefined);
44629
+ } catch {
44630
+ return err({ tag: "write_failed" });
44631
+ }
44632
+ }
44633
+ async function syncDirectory(directory) {
44634
+ if (process.platform === "win32") return true;
44635
+ let handle;
44636
+ try {
44637
+ handle = await open(directory, "r");
44638
+ await handle.sync();
44639
+ return true;
44640
+ } catch {
44641
+ return false;
44642
+ } finally {
44643
+ await handle?.close().catch(() => undefined);
44644
+ }
44645
+ }
44646
+ function mapAtomicBinaryWriteFailure(error) {
44647
+ return isNodeError(error) && (error.code === "ELOOP" || error.code === "EISDIR" || error.code === "ENOTDIR") ? { tag: "unsafe_destination" } : { tag: "write_failed" };
44648
+ }
44649
+ async function writeTemporaryBinaryFile(temporaryPath, data, mode) {
44650
+ let handle;
44651
+ try {
44652
+ handle = await open(temporaryPath, "wx", mode);
44653
+ await handle.writeFile(data);
44654
+ await handle.sync();
44655
+ await handle.close();
44656
+ handle = undefined;
44657
+ await chmod(temporaryPath, mode);
44658
+ } finally {
44659
+ await handle?.close().catch(() => undefined);
44660
+ }
44661
+ }
44662
+ async function finalizeTemporaryBinaryFile(temporaryPath, filePath, overwrite) {
44663
+ if (overwrite === "allow") {
44664
+ await rename(temporaryPath, filePath);
44665
+ return ok([]);
44666
+ }
44667
+ try {
44668
+ await link(temporaryPath, filePath);
44669
+ } catch (error) {
44670
+ return isNodeError(error) && error.code === "EEXIST" ? err({ tag: "destination_exists" }) : err(mapAtomicBinaryWriteFailure(error));
44671
+ }
44672
+ try {
44673
+ await unlink$1(temporaryPath);
44674
+ return ok([]);
44675
+ } catch {
44676
+ return ok([{
44677
+ tag: "temporary_cleanup_failed",
44678
+ temporaryPath
44679
+ }]);
44680
+ }
44681
+ }
44682
+ function toAtomicBinaryWriteOutcome(warnings) {
44683
+ const [first, ...rest] = warnings;
44684
+ return first === undefined ? { tag: "written" } : {
44685
+ tag: "written_with_warnings",
44686
+ warnings: [first, ...rest]
44687
+ };
44688
+ }
44689
+ async function writeBinaryFileAtomic(filePath, data, options) {
44690
+ const parent = dirname$1(filePath);
44691
+ const mode = options.mode ?? 384;
44692
+ const suffix = options.randomSuffix ?? (() => `${process.pid}-${Date.now()}-${randomUUID()}`);
44693
+ let temporaryPath;
44694
+ try {
44695
+ if (options.ensureParent === true) await mkdir(parent, { recursive: true });
44696
+ const admitted = await inspectDestination(filePath, options.overwrite);
44697
+ if (!admitted.ok) return admitted;
44698
+ temporaryPath = join(parent, `.${basename(filePath)}.${suffix()}.tmp`);
44699
+ await writeTemporaryBinaryFile(temporaryPath, data, mode);
44700
+ const finalized = await finalizeTemporaryBinaryFile(temporaryPath, filePath, options.overwrite);
44701
+ if (!finalized.ok) return finalized;
44702
+ temporaryPath = undefined;
44703
+ const warnings = await syncDirectory(parent) ? finalized.value : [...finalized.value, { tag: "directory_sync_failed" }];
44704
+ return ok(toAtomicBinaryWriteOutcome(warnings));
44705
+ } catch (error) {
44706
+ return err(mapAtomicBinaryWriteFailure(error));
44707
+ } finally {
44708
+ if (temporaryPath) await unlink$1(temporaryPath).catch(() => undefined);
44709
+ }
44710
+ }
44711
+
43125
44712
  //#endregion
43126
44713
  //#region src/media/local-media.ts
43127
44714
  const MEDIA_DIR_NAME = "media";
@@ -43143,10 +44730,10 @@ const listMediaDirFiles = async (mediaDir) => {
43143
44730
  }
43144
44731
  };
43145
44732
  const findMatchingFilename = async (contentDir, identifier, manifest) => {
43146
- const mediaDir = path.join(contentDir, MEDIA_DIR_NAME);
44733
+ const mediaDir = path$1.join(contentDir, MEDIA_DIR_NAME);
43147
44734
  const manifestEntry = manifest.assets[identifier];
43148
44735
  if (manifestEntry?.filename) {
43149
- const candidatePath = path.join(mediaDir, manifestEntry.filename);
44736
+ const candidatePath = path$1.join(mediaDir, manifestEntry.filename);
43150
44737
  if (await fileExists(candidatePath)) {
43151
44738
  return manifestEntry.filename;
43152
44739
  }
@@ -43154,7 +44741,7 @@ const findMatchingFilename = async (contentDir, identifier, manifest) => {
43154
44741
  const files = await listMediaDirFiles(mediaDir);
43155
44742
  const matches = files.filter((name) => {
43156
44743
  if (name.startsWith(".")) return false;
43157
- const parsed = path.parse(name);
44744
+ const parsed = path$1.parse(name);
43158
44745
  return parsed.name === identifier;
43159
44746
  });
43160
44747
  if (matches.length > 1) {
@@ -43165,9 +44752,13 @@ const findMatchingFilename = async (contentDir, identifier, manifest) => {
43165
44752
  async function loadLocalMediaFile(contentDir, identifier, manifest) {
43166
44753
  const filename = await findMatchingFilename(contentDir, identifier, manifest);
43167
44754
  if (!filename) return null;
43168
- const mediaDir = path.join(contentDir, MEDIA_DIR_NAME);
43169
- const filePath = path.join(mediaDir, filename);
43170
- const data = await fs.readFile(filePath);
44755
+ const mediaDir = path$1.join(contentDir, MEDIA_DIR_NAME);
44756
+ const filePath = path$1.join(mediaDir, filename);
44757
+ const read = await readStableBinaryFile(filePath, MAX_MANAGEMENT_MEDIA_UPLOAD_BYTES);
44758
+ if (!read.ok) {
44759
+ throw new Error(`Failed to read media file ${filePath}: ${read.error.tag}`);
44760
+ }
44761
+ const data = read.value;
43171
44762
  const checksum = sha256(data);
43172
44763
  const mimeType = getMimeType(filename);
43173
44764
  manifest.assets[identifier] = {
@@ -43186,15 +44777,31 @@ async function loadLocalMediaFile(contentDir, identifier, manifest) {
43186
44777
  };
43187
44778
  }
43188
44779
  async function writeLocalMediaFile(contentDir, filename, data) {
43189
- const mediaDir = path.join(contentDir, MEDIA_DIR_NAME);
44780
+ const mediaDir = path$1.join(contentDir, MEDIA_DIR_NAME);
43190
44781
  await fs.mkdir(mediaDir, { recursive: true });
43191
- const filePath = path.join(mediaDir, filename);
43192
- await fs.writeFile(filePath, data);
43193
- return filePath;
44782
+ const filePath = path$1.join(mediaDir, filename);
44783
+ const written = await writeBinaryFileAtomic(filePath, data, {
44784
+ overwrite: "allow",
44785
+ mode: 420
44786
+ });
44787
+ if (!written.ok) {
44788
+ throw new Error(`Failed to write media file ${filePath}: ${written.error.tag}`);
44789
+ }
44790
+ return written.value.tag === "written" ? {
44791
+ kind: "written",
44792
+ filePath
44793
+ } : {
44794
+ kind: "written_with_warnings",
44795
+ filePath,
44796
+ warnings: written.value.warnings
44797
+ };
43194
44798
  }
43195
- async function readLocalMediaChecksum(filePath) {
43196
- const data = await fs.readFile(filePath);
43197
- return sha256(data);
44799
+ async function readLocalMediaChecksum(filePath, maxBytes) {
44800
+ const read = await readStableBinaryFile(filePath, maxBytes);
44801
+ if (!read.ok) {
44802
+ throw new Error(`Failed to read media file ${filePath}: ${read.error.tag}`);
44803
+ }
44804
+ return sha256(read.value);
43198
44805
  }
43199
44806
  async function localMediaFileExists(filePath) {
43200
44807
  return fileExists(filePath);
@@ -43259,7 +44866,7 @@ const parseFilenameFromUrl = (src) => {
43259
44866
  }
43260
44867
  };
43261
44868
  const ensureFilenameExtension = (filename, mimeType) => {
43262
- if (path.extname(filename)) return filename;
44869
+ if (path$1.extname(filename)) return filename;
43263
44870
  const ext = getExtensionForMimeType(mimeType);
43264
44871
  return ext ? `${filename}${ext}` : filename;
43265
44872
  };
@@ -44436,7 +46043,7 @@ const removeStaleLocalFile = async (options) => {
44436
46043
  if (options.dryRun || !options.previousFilename || options.previousFilename === options.filename) {
44437
46044
  return;
44438
46045
  }
44439
- const previousPath = path.join(options.contentDir, "media", options.previousFilename);
46046
+ const previousPath = path$1.join(options.contentDir, "media", options.previousFilename);
44440
46047
  if (await localMediaFileExists(previousPath)) {
44441
46048
  await fs.unlink(previousPath).catch(() => {});
44442
46049
  }
@@ -44467,7 +46074,8 @@ const applyDownloadDecision = async (decision, context) => {
44467
46074
  context.warnings.push(`Failed to download media for "${decision.identifier}" from ${urlResult.value}.`);
44468
46075
  return;
44469
46076
  }
44470
- await writeLocalMediaFile(context.contentDir, decision.filename, downloaded.data);
46077
+ const written = await writeLocalMediaFile(context.contentDir, decision.filename, downloaded.data);
46078
+ context.warnings.push(...localMediaWriteWarningMessages(decision.identifier, written));
44471
46079
  context.manifest.assets[decision.identifier] = {
44472
46080
  checksum: sha256(downloaded.data),
44473
46081
  filename: decision.filename,
@@ -44484,7 +46092,7 @@ const syncPullMediaDownload = async (planned, indexes, context) => {
44484
46092
  return;
44485
46093
  }
44486
46094
  const filename = buildLocalFilename(identifier, asset);
44487
- const localFilePath = path.join(context.contentDir, "media", filename);
46095
+ const localFilePath = path$1.join(context.contentDir, "media", filename);
44488
46096
  const existingEntry = context.manifest.assets[identifier];
44489
46097
  await removeStaleLocalFile({
44490
46098
  contentDir: context.contentDir,
@@ -44522,10 +46130,16 @@ const syncPullMediaDownloads = async (options) => {
44522
46130
  }
44523
46131
  if (!options.dryRun) options.summary.pendingDownloads = 0;
44524
46132
  };
44525
- const sha256FromFile = async (filePath) => {
44526
- const buffer = await fs.readFile(filePath);
44527
- return sha256(buffer);
44528
- };
46133
+ const sha256FromFile = (filePath) => readLocalMediaChecksum(filePath, FILE_SIZE_LIMITS.rawVideoBeforeCompression);
46134
+ function localMediaWriteWarningMessages(identifier, outcome) {
46135
+ if (outcome.kind === "written") return [];
46136
+ return outcome.warnings.map((warning) => {
46137
+ switch (warning.tag) {
46138
+ case "temporary_cleanup_failed": return `Downloaded media for "${identifier}", but could not remove temporary file ${warning.temporaryPath}.`;
46139
+ case "directory_sync_failed": return `Downloaded media for "${identifier}", but could not confirm the destination directory was synced.`;
46140
+ }
46141
+ });
46142
+ }
44529
46143
 
44530
46144
  //#endregion
44531
46145
  //#region src/media/pull-workflow.ts
@@ -44945,4 +46559,4 @@ Examples:
44945
46559
  }
44946
46560
 
44947
46561
  //#endregion
44948
- export { require_jsx_runtime as $, NAV_LINK_LETTER_SPACING_MAP as $n, surfaceForegroundDeclarations as $r, formatIgnoredDocumentSyncSectionWarning as $t, getImageUrl as A, __toESM as Ai, MQ_SM_DOWN as An, isCssColorLiteral as Ar, parseJsonData as At, createDryRunReport as B, eyebrowTreatmentValues as Bn, resolveSurfaceForeground as Br, planEnvironmentExecution as Bt, rotateNormalizedPoint as C, internalResolvedLinkSchema as Ci, BREAKPOINT_LG as Cn, RESOLVED_BUTTON_DISABLED_DECLARATIONS as Cr, currentInvocationEnvelope as Ct, resolveMediaTransformForAspect as D, linkSchema as Di, MQ_LG_UP as Dn, contextualButtonSurfaceStyles as Dr, handleCommandError as Dt, parseAspectRatioValue as E, isResolvedInternalLinkEntityType as Ei, BREAKPOINT_XL as En, contextualButtonSurfaceResetStyles as Er, getOutputOnlyContext as Et, parseRelativeStoragePath as F, assertNever$5 as Fn, borderColorStyle as Fr, withConfirmation as Ft, getSiteCommandMetadata as G, testimonialsCompositionTreatmentValues as Gn, TokenResolver as Gr, CliExit as Gt, mapEventCommandContent as H, headingDecorationColorTokenValues as Hn, generateEffectsCSS as Hr, requireEnvironmentWriteAuthorization as Ht, classifyKnownMimeType as I, defineBlockDesignCapability as In, cssVarValue as Ir, withErrorHandling as It, entryIdentifierForCommand as J, getComponentBorderWidthPx as Jn, parseThemeForPersistence as Jr, getEnvironmentDebugInfo as Jt, blockIdentifierForCommand as K, tileTreatmentValues as Kn, parseThemeButtonVariantId as Kr, getEnvPrefix as Kt, venueManagementFields as L, defineFrameCapability as Ln, mergeStyles as Lr, withOutputOnlyErrorHandling as Lt, deriveVideoSlotPolicy as M, MQ_TABLET_ONLY as Mn, resolveThemeColorReferenceValue as Mr, resolveCommandPath as Mt, resolveVideoSelectionIntentForMediaField as N, headerVariantValues as Nn, resolveTokenOrCssColorValue as Nr, runWithInvocation as Nt, getDirectImageUrlForRelativePath as O, pageLinkSchema$1 as Oi, MQ_MD_DOWN as On, deriveButtonSurfaceContext as Or, mapEntryForOutput as Ot, asRecord as P, pageDesignEditorBackgroundOverrideOwner as Pn, backgroundColorStyle as Pr, withCommandContext as Pt, hasConfiguredBookingVertical as Q, NAV_LINK_BORDER_RADIUS_MAP as Qn, DEFAULT_SURFACE_TEXT_COLOR as Qr, createManagementClient as Qt, sortRecord as R, defineNoBlockDesignCapability as Rn, rgbColorValue as Rr, executeEnvironmentPlan as Rt, deleteEnvSpecificKeys as S, internalLinkSchema as Si, renderHeaderPaintTargetRules as Sn, generateButtonCss as Sr, createOutputForCommand as St, isExplicitRectCrop as T, isInternalResolvedLinkValue as Ti, BREAKPOINT_SM as Tn, resolvedButtonFocusDeclarations as Tr, getOutputContext as Tt, evaluateSiteCommandBatchExposure as U, introCompositionTreatmentValues as Un, generateBaseInteractiveCSS as Ur, withRiverbankEnv as Ut, parseAndPlanCommandBatchForTypes as V, featureCardCompositionTreatmentValues as Vn, generateDefaultButtonSystem as Vr, planEnvironmentWriteAuthorization as Vt, evaluateSiteCommandExposure as W, offeringsPreviewCompositionTreatmentValues as Wn, generateInteractiveChildInheritanceCSS as Wr, formatError as Wt, pageIdentifierForCommand as X, parseBlockCustomCssRules as Xn, formatButtonColourContractError as Xr, isRemoteConfigured as Xt, pageIdentifierForBlockCommand as Y, getComponentShadowVar as Yn, compileButtonColourStates as Yr, isLocalConfigured as Yt, pagePathForCommand as Z, parseCustomCssRules as Zn, generateShadowValue$1 as Zr, loadSdkEnvironment as Zt, hasOwnSettingsField as _, linkPayloadSchema as _i, boxBackgroundOverlaySchema as _n, cardRoleSchema as _r, confirmAction as _t, syncLocalMediaForPull as a, getTextColorForBackground as ai, GRADIENT_PRESET_IDS as an, INPUT_SPACING_MAP as ar, createCommandRef as at, attachRenderableMediaAssetFields as b, entryLinkSchema$1 as bi, generateMobileOverlayBackdropFilterFix as bn, PUBLIC_BUTTON_ROLES as br, createGetCommand as bt, sha256 as c, hexToRgb$1 as ci, boundaryRuntimeMetricsForTransition as cn, ACCORDION_SPACING_MAP as cr, createEventSeriesRef as ct, walkLocalContentMedia as d, unsafeAsOpaqueButtonColourToken as di, parseBackgroundAppearanceColorToken as dn, ICON_CONFIG as dr, createNavigationRef as dt, surfaceForegroundStyle as ei, ManagementApiError as en, NAV_LINK_PADDING_MAP as er, require_react as et, buildStorageUrl as f, parseBlockColorToken as fi, defaultThemeTokens as fn, ICON_SIZE_PX as fr, createPageRef as ft, buildSettingsUpdateInput as g, persistableMediaSchema as gi, themeSchema as gn, generateShadowValue as gr, slugPart as gt, SETTINGS_FILE_FIELDS as h, mediaTypeDiscriminants as hi, sdkThemeWireSchema as hn, generateCardCss as hr, refSlug as ht, syncLocalMediaForPush as i, onColorTokenName as ii, gradientToCss as in, INPUT_FONT_SIZE_MAP as ir, createBlocksRef as it, isSiteScopedDirectImageStoragePath as j, MQ_SM_UP as jn, resolveOnColorForThemeToken as jr, parsePaginationOptions as jt, getDirectImageUrlForSiteScopedPath as k, sdkConfiguredBookingVerticalConfigSchema as ki, MQ_MD_UP as kn, resolveContextualButtonRole as kr, parseJsonArray as kt, normalizeMediaIdentifierInput as l, VARIANT_ROLES as li, backgroundAppearanceColorTokenNames as ln, ACCORDION_TRIGGER_FONT_SCALE as lr, createFooterRef as lt, extractMediaIdentifiers as m, mediaSchema as mi, sdkSettingsUpdateSchema as mn, getDefaultCardVariants as mr, createVenueRef as mt, createStyleCommand as n, expandPalette as ni, readHeaderNavFontSizePreference as nn, NAV_LINK_TEXT_SIZE_MAP as nr, generateCssVars as nt, applyLocalMediaPush as o, ORDINARY_TEXT_MIN_CONTRAST_RATIO as oi, getGradientPreset as on, LABEL_FONT_SIZE_MAP as or, createContentEntryRef as ot, downloadMedia as p, mediaIdentifierSchema as pi, defineBlockDesignMetadata as pn, generateCardCoreCss as pr, createSiteCommandSiteRef as pt, blockOrderIdentifiersForCommand as q, visibleHeadingDecorationFamilyValues as qn, themeButtonClassName as qr, getEnvVarName as qt, revalidateLocalMediaPushPlan as r, isOnColorBackgroundCandidateToken as ri, getCuratedSiteStyle as rn, FONT_WEIGHT_MAP as rr, createBlockRef as rt, prepareLocalMediaForPush as s, featuresFromHex as si, parseGradientPresetId as sn, ACCORDION_OPEN_ACCENT_THICKNESS as sr, createEventCategoryRef as st, buildStyleSelectionInitializeCommandFromOptions as t, surfaceForegroundValue as ti, mergeHeaderNavTypographyPreference as tn, NAV_LINK_PADDING_X_MAP as tr, pickShadeTowardBackground as tt, normalizeMediaValueToIdentifierOnly as u, buttonVariantIdSchema as ui, backgroundAppearanceQuickColorTokenNames as un, ACCORDION_TRIGGER_LETTER_SPACING as ur, createFormRef as ut, writeMediaManifest as v, RESOLVED_INTERNAL_LINK_ENTITY_TYPES as vi, boxBackgroundSchema as vn, cardVariantRoleValues as vr, createClientForEnv as vt, normalizeWidthCandidates as w, internalRouteOnlyLinkSchema as wi, BREAKPOINT_MD as wn, resolvedButtonColourDeclarations as wr, formatDateShort as wt, buildRenderableMediaAssetIndex as x, externalLinkSchema$1 as xi, getHeaderPaintTargets as xn, AFFORDANCE_SUPPRESS_CLASS as xr, createListCommand$1 as xt, isMediaValue as y, customLinkSchema$1 as yi, generateHeaderScrollTreatmentStyles as yn, normalizeCardRole as yr, createCommandContext as yt, stableStringify as z, eventListTreatmentValues as zn, textColorStyle as zr, parseEnvironmentSelection as zt };
46562
+ export { mapEventCommandContent as $, headingDecorationColorTokenValues as $n, generateEffectsCSS as $r, requireEnvironmentWriteAuthorization as $t, isExplicitRectCrop as A, customLinkSchema$1 as Ai, boxBackgroundSchema as An, normalizeCardRole as Ar, createCommandContext as At, resolveVideoSelectionIntentForMediaField as B, sdkConfiguredBookingVerticalConfigSchema as Bi, MQ_MD_DOWN as Bn, resolveContextualButtonRole as Br, parseJsonArray as Bt, attachRenderableMediaAssetFields as C, parseBlockColorToken as Ci, parseBackgroundAppearanceColorToken as Cn, ICON_SIZE_PX as Cr, createPageRef as Ct, looksLikeUuid as D, persistableMediaSchema as Di, sdkThemeWireSchema as Dn, generateShadowValue as Dr, slugPart as Dt, deleteEnvSpecificKeys as E, mediaTypeDiscriminants as Ei, sdkSettingsUpdateSchema as En, generateCardCss as Er, refSlug as Et, getImageUrl as F, internalRouteOnlyLinkSchema as Fi, BREAKPOINT_LG as Fn, resolvedButtonColourDeclarations as Fr, formatDateShort as Ft, classifyKnownMimeType as G, pageDesignEditorBackgroundOverrideOwner as Gn, backgroundColorStyle as Gr, withCommandContext as Gt, asRecord as H, MQ_SM_UP as Hn, resolveOnColorForThemeToken as Hr, parsePaginationOptions as Ht, isSiteScopedDirectImageStoragePath as I, isInternalResolvedLinkValue as Ii, BREAKPOINT_MD as In, resolvedButtonFocusDeclarations as Ir, getOutputContext as It, venueManagementFields as J, defineFrameCapability as Jn, mergeStyles as Jr, withOutputOnlyErrorHandling as Jt, managementMediaLibraryUploadLimitForMimeType as K, assertNever$5 as Kn, borderColorStyle as Kr, withConfirmation as Kt, buildMediaIdentifierFromFilename as L, isResolvedInternalLinkEntityType as Li, BREAKPOINT_SM as Ln, contextualButtonSurfaceResetStyles as Lr, getOutputOnlyContext as Lt, resolveMediaTransformForAspect as M, externalLinkSchema$1 as Mi, generateMobileOverlayBackdropFilterFix as Mn, AFFORDANCE_SUPPRESS_CLASS as Mr, createListCommand$1 as Mt, getDirectImageUrlForRelativePath as N, internalLinkSchema as Ni, getHeaderPaintTargets as Nn, generateButtonCss as Nr, createOutputForCommand as Nt, rotateNormalizedPoint as O, linkPayloadSchema as Oi, themeSchema as On, cardRoleSchema as Or, confirmAction as Ot, getDirectImageUrlForSiteScopedPath as P, internalResolvedLinkSchema as Pi, renderHeaderPaintTargetRules as Pn, RESOLVED_BUTTON_DISABLED_DECLARATIONS as Pr, currentInvocationEnvelope as Pt, parseAndPlanCommandBatchForTypes as Q, featureCardCompositionTreatmentValues as Qn, generateDefaultButtonSystem as Qr, planEnvironmentWriteAuthorization as Qt, parseMediaIdentifier as R, linkSchema as Ri, BREAKPOINT_XL as Rn, contextualButtonSurfaceStyles as Rr, handleCommandError as Rt, isMediaValue as S, unsafeAsOpaqueButtonColourToken as Si, backgroundAppearanceQuickColorTokenNames as Sn, ICON_CONFIG as Sr, createNavigationRef as St, walkAndCollectImageAttrs as T, mediaSchema as Ti, defineBlockDesignMetadata as Tn, getDefaultCardVariants as Tr, createVenueRef as Tt, parseRelativeStoragePath as U, MQ_TABLET_ONLY as Un, resolveThemeColorReferenceValue as Ur, resolveCommandPath as Ut, getMimeType as V, __toESM as Vi, MQ_SM_DOWN as Vn, isCssColorLiteral as Vr, parseJsonData as Vt, FILE_SIZE_LIMITS as W, headerVariantValues as Wn, resolveTokenOrCssColorValue as Wr, runWithInvocation as Wt, stableStringify as X, eventListTreatmentValues as Xn, textColorStyle as Xr, parseEnvironmentSelection as Xt, sortRecord as Y, defineNoBlockDesignCapability as Yn, rgbColorValue as Yr, executeEnvironmentPlan as Yt, createDryRunReport as Z, eyebrowTreatmentValues as Zn, resolveSurfaceForeground as Zr, planEnvironmentExecution as Zt, extractMediaIdentifiers as _, ORDINARY_TEXT_MIN_CONTRAST_RATIO as _i, GRADIENT_PRESET_IDS as _n, LABEL_FONT_SIZE_MAP as _r, createContentEntryRef as _t, syncLocalMediaForPull as a, parseThemeForPersistence as ai, getEnvironmentDebugInfo as an, getComponentBorderWidthPx as ar, entryIdentifierForCommand as at, hasOwnSettingsField as b, VARIANT_ROLES as bi, boundaryRuntimeMetricsForTransition as bn, ACCORDION_TRIGGER_FONT_SCALE as br, createFooterRef as bt, admitBinaryWriteDestination as c, generateShadowValue$1 as ci, loadSdkEnvironment as cn, parseCustomCssRules as cr, pagePathForCommand as ct, sha256 as d, surfaceForegroundStyle as di, ManagementApiError as dn, NAV_LINK_PADDING_MAP as dr, require_react as dt, generateBaseInteractiveCSS as ei, withRiverbankEnv as en, introCompositionTreatmentValues as er, evaluateSiteCommandBatchExposure as et, normalizeMediaIdentifierInput as f, surfaceForegroundValue as fi, retargetCompiledBoundaryRuntimeForContentAdjacency as fn, NAV_LINK_PADDING_X_MAP as fr, pickShadeTowardBackground as ft, downloadMedia as g, getTextColorForBackground as gi, gradientToCss as gn, INPUT_SPACING_MAP as gr, createCommandRef as gt, buildStorageUrl as h, onColorTokenName as hi, getCuratedSiteStyle as hn, INPUT_FONT_SIZE_MAP as hr, createBlocksRef as ht, syncLocalMediaForPush as i, themeButtonClassName as ii, getEnvVarName as in, visibleHeadingDecorationFamilyValues as ir, blockOrderIdentifiersForCommand as it, parseAspectRatioValue as j, entryLinkSchema$1 as ji, generateHeaderScrollTreatmentStyles as jn, PUBLIC_BUTTON_ROLES as jr, createGetCommand as jt, normalizeWidthCandidates as k, RESOLVED_INTERNAL_LINK_ENTITY_TYPES as ki, boxBackgroundOverlaySchema as kn, cardVariantRoleValues as kr, createClientForEnv as kt, readStableBinaryFile as l, DEFAULT_SURFACE_TEXT_COLOR as li, createManagementClient as ln, NAV_LINK_BORDER_RADIUS_MAP as lr, hasConfiguredBookingVertical as lt, walkLocalContentMedia as m, isOnColorBackgroundCandidateToken as mi, readHeaderNavFontSizePreference as mn, FONT_WEIGHT_MAP as mr, createBlockRef as mt, createStyleCommand as n, TokenResolver as ni, CliExit as nn, testimonialsCompositionTreatmentValues as nr, getSiteCommandMetadata as nt, applyLocalMediaPush as o, compileButtonColourStates as oi, isLocalConfigured as on, getComponentShadowVar as or, pageIdentifierForBlockCommand as ot, normalizeMediaValueToIdentifierOnly as p, expandPalette as pi, mergeHeaderNavTypographyPreference as pn, NAV_LINK_TEXT_SIZE_MAP as pr, generateCssVars as pt, parseSafeDownloadFilename as q, defineBlockDesignCapability as qn, cssVarValue as qr, withErrorHandling as qt, revalidateLocalMediaPushPlan as r, parseThemeButtonVariantId as ri, getEnvPrefix as rn, tileTreatmentValues as rr, blockIdentifierForCommand as rt, prepareLocalMediaForPush as s, formatButtonColourContractError as si, isRemoteConfigured as sn, parseBlockCustomCssRules as sr, pageIdentifierForCommand as st, buildStyleSelectionInitializeCommandFromOptions as t, generateInteractiveChildInheritanceCSS as ti, formatError as tn, offeringsPreviewCompositionTreatmentValues as tr, evaluateSiteCommandExposure as tt, writeBinaryFileAtomic as u, surfaceForegroundDeclarations as ui, formatIgnoredDocumentSyncSectionWarning as un, NAV_LINK_LETTER_SPACING_MAP as ur, require_jsx_runtime as ut, SETTINGS_FILE_FIELDS as v, featuresFromHex as vi, getGradientPreset as vn, ACCORDION_OPEN_ACCENT_THICKNESS as vr, createEventCategoryRef as vt, buildRenderableMediaAssetIndex as w, mediaIdentifierSchema as wi, defaultThemeTokens as wn, generateCardCoreCss as wr, createSiteCommandSiteRef as wt, writeMediaManifest as x, buttonVariantIdSchema as xi, backgroundAppearanceColorTokenNames as xn, ACCORDION_TRIGGER_LETTER_SPACING as xr, createFormRef as xt, buildSettingsUpdateInput as y, hexToRgb$1 as yi, parseGradientPresetId as yn, ACCORDION_SPACING_MAP as yr, createEventSeriesRef as yt, deriveVideoSlotPolicy as z, pageLinkSchema$1 as zi, MQ_LG_UP as zn, deriveButtonSurfaceContext as zr, mapEntryForOutput as zt };