riverbankcms 0.4.3 → 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
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
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-CeNepTFW.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
@@ -10474,12 +10601,33 @@ const headerContainerSchema = z.object({
10474
10601
 
10475
10602
  //#endregion
10476
10603
  //#region ../theme-core/src/header/interactionPalette.ts
10477
- 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 = [
10478
10609
  "primary",
10479
10610
  "secondary",
10480
10611
  "accent"
10481
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];
10482
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
+ */
10483
10631
  function headerInteractionPalettePair(palette) {
10484
10632
  switch (palette) {
10485
10633
  case "primary": return {
@@ -10879,11 +11027,12 @@ const inputStyle = z.object({
10879
11027
  ]),
10880
11028
  label: z.enum(["inside", "above"])
10881
11029
  });
10882
- const headerPositioning = z.enum([
11030
+ const headerPositioningValues = [
10883
11031
  "static",
10884
11032
  "sticky",
10885
11033
  "fixed"
10886
- ]);
11034
+ ];
11035
+ const headerPositioning = z.enum(headerPositioningValues);
10887
11036
  const headerNavStyle = z.enum([
10888
11037
  "minimal",
10889
11038
  "underline",
@@ -11018,7 +11167,7 @@ const headerSchema = z.object({
11018
11167
  variant: headerVariantWithLegacyAliasSchema,
11019
11168
  positioning: headerPositioning,
11020
11169
  shrinkOnScroll: z.boolean(),
11021
- maxWidth: headerMaxWidth,
11170
+ maxWidth: headerMaxWidth.default("container"),
11022
11171
  scrollTreatment: headerScrollTreatmentSchema,
11023
11172
  background: headerBackgroundSchema.default({
11024
11173
  type: "color",
@@ -11212,11 +11361,6 @@ const bodyTextSchema = z.object({ intro: z.object({
11212
11361
  paragraphSize: bodyTextIntroTypographySizeSchema.optional(),
11213
11362
  headingSize: bodyTextIntroTypographySizeSchema.optional()
11214
11363
  }).optional() }).optional();
11215
- const gradientsSchema = z.object({
11216
- button: z.string().min(1).nullable().optional(),
11217
- hero: z.string().min(1).nullable().optional(),
11218
- background: z.string().min(1).nullable().optional()
11219
- });
11220
11364
  /**
11221
11365
  * Testimonial quote-mark treatment, owned by the site style (analogous to the
11222
11366
  * accordion icon). `style` is the coarse selector; the optional fields fine-tune
@@ -11832,7 +11976,7 @@ function resolveAccessibleSurface(input) {
11832
11976
  threshold,
11833
11977
  rejectedCandidates
11834
11978
  });
11835
- const link = resolveRequiredForeground({
11979
+ const link$1 = resolveRequiredForeground({
11836
11980
  role: "link",
11837
11981
  candidates: recipe.linkCandidates,
11838
11982
  backgroundToken,
@@ -11841,7 +11985,7 @@ function resolveAccessibleSurface(input) {
11841
11985
  threshold,
11842
11986
  rejectedCandidates
11843
11987
  });
11844
- if (!heading || !text || !link) continue;
11988
+ if (!heading || !text || !link$1) continue;
11845
11989
  const supporting = resolveSupportingForeground({
11846
11990
  candidates: recipe.supportingCandidates,
11847
11991
  backgroundToken,
@@ -11861,7 +12005,7 @@ function resolveAccessibleSurface(input) {
11861
12005
  },
11862
12006
  heading,
11863
12007
  text,
11864
- link,
12008
+ link: link$1,
11865
12009
  supporting,
11866
12010
  rejectedCandidates
11867
12011
  };
@@ -13303,6 +13447,32 @@ function replaceSiteStyleCompilationPlan(input) {
13303
13447
  footerLookId: input.footerLookId ?? null
13304
13448
  };
13305
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
+ }
13306
13476
 
13307
13477
  //#endregion
13308
13478
  //#region ../theme-core/src/site-styles/schemaVersionedJson.ts
@@ -16465,6 +16635,62 @@ const overlayLogoAccommodations = [
16465
16635
  inlineMarkWithTitleLogoAccommodation,
16466
16636
  unbackedOverlayLogoAccommodation
16467
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
+ }
16468
16694
  function logoFactsForHeaderContext(logo) {
16469
16695
  if (logo.kind === "facts") return logo.facts;
16470
16696
  switch (logo.shape) {
@@ -16505,22 +16731,6 @@ function requireHeaderBrandPresentationSelection(plan) {
16505
16731
  if (plan.kind === "selected") return plan;
16506
16732
  throw new Error(`Selected header layout did not have a compatible logo accommodation: ${plan.reasons.join(", ")}`);
16507
16733
  }
16508
- /**
16509
- * Starts each compilation from the generated theme's header branch, then
16510
- * clears optional nested styling fields that would otherwise leak from a
16511
- * different header selection.
16512
- */
16513
- function baseHeader(theme) {
16514
- 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;
16515
- return {
16516
- ...header,
16517
- navContainer: {
16518
- type: "none",
16519
- tint: null,
16520
- opacity: .15
16521
- }
16522
- };
16523
- }
16524
16734
  const headerLayoutCatalog = defineExactCatalog()([
16525
16735
  {
16526
16736
  id: asHeaderLayoutId("classic"),
@@ -16854,6 +17064,10 @@ const clearSplitInteractionPalette = defineLookOptionGroup({
16854
17064
  {
16855
17065
  id: "secondary",
16856
17066
  label: "Secondary"
17067
+ },
17068
+ {
17069
+ id: headerInteractionQuietPalette,
17070
+ label: "Text only"
16857
17071
  }
16858
17072
  ]
16859
17073
  });
@@ -16887,10 +17101,11 @@ function availableControlState(group, preference, effectivePalette, repair$1) {
16887
17101
  }
16888
17102
  function applyHeaderInteractionPalette(header, group, preference) {
16889
17103
  if (!group) return header;
17104
+ const palette = resolveHeaderInteractionPalette(group, preference).option.id;
17105
+ if (palette === headerInteractionQuietPalette) return header;
16890
17106
  if (!header.navLinkStyle) {
16891
17107
  throw new Error(`Header interaction palette "${group.id}" requires a nav link style.`);
16892
17108
  }
16893
- const palette = resolveHeaderInteractionPalette(group, preference).option.id;
16894
17109
  const pair = headerInteractionPalettePair(palette);
16895
17110
  const pillEffect = {
16896
17111
  effectId: "background-on-hover",
@@ -16919,6 +17134,103 @@ function resolveHeaderInteractionPalette(group, preference) {
16919
17134
  function defaultOptionLabel(group) {
16920
17135
  return resolveLookOption(group, undefined).option.label;
16921
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
+ }
16922
17234
 
16923
17235
  //#endregion
16924
17236
  //#region ../theme-core/src/site-styles/headerStyleDefaults.ts
@@ -17589,10 +17901,37 @@ function recipeCta(cta, theme) {
17589
17901
  ...cta.gap ? { ctaGap: cta.gap } : {}
17590
17902
  };
17591
17903
  }
17592
- /** Layout-derived structure. The only part of the header a recipe cannot reach. */
17593
- function headerForLayout(theme, layout) {
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) {
17594
17928
  return {
17595
- ...baseHeader(theme),
17929
+ maxWidth: "container",
17930
+ navContainer: {
17931
+ type: "none",
17932
+ tint: null,
17933
+ opacity: .15
17934
+ },
17596
17935
  variant: layout.variant,
17597
17936
  positioning: layout.defaultPositioning,
17598
17937
  shrinkOnScroll: layout.shrinkOnScroll,
@@ -17607,12 +17946,19 @@ function headerForLayout(theme, layout) {
17607
17946
  /**
17608
17947
  * The single header interpreter. Pure and total: same `(theme, layout, recipe)`
17609
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.
17610
17956
  */
17611
17957
  function compileHeaderRecipe(theme, layout, recipe) {
17612
17958
  const effective = effectiveRecipe(layout, recipe);
17613
17959
  const navLinkStyle = recipeNavLinkStyle(effective);
17614
17960
  return {
17615
- ...headerForLayout(theme, layout),
17961
+ ...headerForLayout(layout),
17616
17962
  textColor: toneColorToken(effective.tone),
17617
17963
  navColor: toneColorToken(effective.tone),
17618
17964
  dropdownStyle: toneDropdownStyle(effective.tone),
@@ -17646,11 +17992,19 @@ function compileHeaderRecipe(theme, layout, recipe) {
17646
17992
  * site style has to say so, because silently falling back to the catalogue
17647
17993
  * default is exactly the ownership leak this slice closes. Pass `null` when
17648
17994
  * there genuinely is no site style (initial site provisioning).
17649
- */
17650
- function compileHeaderLayoutStyleSelection(theme, resolution, siteStyle) {
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) {
17651
18005
  const recipe = resolveHeaderStyleRecipe(siteStyle, resolution.style.id);
17652
18006
  const header = compileHeaderRecipe(theme, resolution.layout, recipe);
17653
- return applyHeaderInteractionPalette(header, recipe.interactionPalette, theme.header.interactionPalette ?? "recommended");
18007
+ return applyHeaderInteractionPalette(header, recipe.interactionPalette, interactionPalette);
17654
18008
  }
17655
18009
 
17656
18010
  //#endregion
@@ -20166,10 +20520,22 @@ function defaultPaletteForSiteStyle(siteStyle) {
20166
20520
  return defaultCuratedChoice(siteStyle.paletteRecommendations);
20167
20521
  }
20168
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) {
20169
20532
  const [{ layout, styles }] = siteStyle.headerCuration;
20170
20533
  return {
20171
- layoutId: layout.id,
20172
- styleId: styles[0].id
20534
+ selection: {
20535
+ layoutId: layout.id,
20536
+ styleId: styles[0].id
20537
+ },
20538
+ styles
20173
20539
  };
20174
20540
  }
20175
20541
  function headerLayoutChoicesForSiteStyle(siteStyle) {
@@ -22724,6 +23090,878 @@ function reinterpretForSiteStyle(input) {
22724
23090
  };
22725
23091
  }
22726
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
+
22727
23965
  //#endregion
22728
23966
  //#region ../theme-core/src/site-styles/runtimeBackgroundResolution.ts
22729
23967
  function parseCanonicalRuntimeBackground(content) {
@@ -23010,43 +24248,56 @@ function isCycleableDesignOption(option) {
23010
24248
  //#endregion
23011
24249
  //#region ../theme-core/src/site-styles/previewCompiler.ts
23012
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
+ }
23013
24283
  function compileHeaderDirective(input) {
23014
24284
  if (input.directive.kind === "preserve") {
23015
- return {
23016
- header: input.directive.header,
23017
- brandPresentation: null,
23018
- resolvedHeaderSelection: input.currentSelection,
23019
- warning: null
23020
- };
24285
+ return compilePreservedHeaderDirective(input.directive, input);
23021
24286
  }
23022
24287
  const requested = input.directive.selection;
23023
- const requestedLayoutChoices = headerStyleChoicesForLayout(input.siteStyle, requested.layoutId);
23024
- const curatedDefault = defaultHeaderSelectionForSiteStyle(input.siteStyle);
23025
- const resolvedLayoutId = requestedLayoutChoices ? requested.layoutId : curatedDefault.layoutId;
23026
- const curatedChoices = requestedLayoutChoices ?? headerStyleChoicesForLayout(input.siteStyle, curatedDefault.layoutId);
23027
- if (!curatedChoices) {
23028
- throw new Error(`Site style ${input.siteStyle.id} has invalid header curation`);
23029
- }
23030
- const resolvedStyleId = deriveNearestCompatibleHeaderStyle(requested.styleId, resolvedLayoutId, curatedChoices.map((choice) => choice.id));
23031
- const resolution = resolveHeaderLayoutStyleSelection({
23032
- layoutId: resolvedLayoutId,
23033
- 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
23034
24291
  });
23035
- const header = compileHeaderLayoutStyleSelection(input.theme, resolution, input.siteStyle);
23036
- const brandPresentation = input.directive.brandPresentation.kind === "apply-logo-context" ? resolveHeaderBrandPresentationForLayout({
23037
- layout: resolution.layout,
23038
- logo: input.directive.brandPresentation.logo
23039
- }) : null;
23040
- const headerWithBrandPresentation = brandPresentation !== null ? applyHeaderBrandPresentation(header, brandPresentation) : header;
23041
24292
  return {
23042
- header: headerWithBrandPresentation,
24293
+ header,
23043
24294
  brandPresentation,
23044
24295
  resolvedHeaderSelection: resolution.selection,
23045
- warning: resolution.selection.layoutId === requested.layoutId && resolution.selection.styleId === requested.styleId ? null : {
24296
+ warning: repair$1.kind === "exact" ? null : {
23046
24297
  kind: "repaired-header-selection",
23047
24298
  requested,
23048
24299
  resolved: resolution.selection,
23049
- reason: requestedLayoutChoices ? "style-not-curated-for-layout" : "layout-not-curated-for-site-style"
24300
+ reason: repair$1.reason
23050
24301
  }
23051
24302
  };
23052
24303
  }
@@ -23080,21 +24331,6 @@ function compileIncrementalFooter(input) {
23080
24331
  resolvedFooterLookId: result.selectedLookId
23081
24332
  };
23082
24333
  }
23083
- function applyHeaderBrandPresentation(header, presentation) {
23084
- const shouldShrinkDisplayLogo = presentation.size === "display" && presentation.accommodation.providesBacking === true && header.positioning !== "static";
23085
- return {
23086
- ...header,
23087
- shrinkOnScroll: shouldShrinkDisplayLogo ? true : header.shrinkOnScroll,
23088
- logo: {
23089
- fontFamily: header.logo?.fontFamily ?? "heading",
23090
- letterSpacing: header.logo?.letterSpacing ?? "normal",
23091
- gradient: header.logo?.gradient ?? false,
23092
- brandDisplay: presentation.display,
23093
- size: presentation.size,
23094
- accommodation: presentation.accommodation.pattern
23095
- }
23096
- };
23097
- }
23098
24334
  function applyFooterBrandPresentation(footer, presentation) {
23099
24335
  const size = footerLogoSizeFromBrandPresentation(presentation.size);
23100
24336
  return {
@@ -24170,6 +25406,123 @@ function runGeneratedPageDesignPipeline(input) {
24170
25406
  };
24171
25407
  }
24172
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
+
24173
25526
  //#endregion
24174
25527
  //#region ../theme-core/src/site-styles/headerOpeningForeground.ts
24175
25528
  function headerStyleAvailability(recipe, context) {
@@ -24609,276 +25962,6 @@ function boundaryPinsEqual(left, right) {
24609
25962
  return left.boundaryId === right.boundaryId && left.previousBlockId === right.previousBlockId && left.nextBlockId === right.nextBlockId && boundaryOptionIdForTreatment(left.choice.value) === boundaryOptionIdForTreatment(right.choice.value);
24610
25963
  }
24611
25964
 
24612
- //#endregion
24613
- //#region ../theme-core/src/site-styles/siteChromeBackgrounds.ts
24614
- const siteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceColorTokenNames);
24615
- const quickSiteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceQuickColorTokenNames);
24616
- const advancedSiteChromeBackgroundColorTokens = siteChromeBackgroundColorTokens.filter((token) => !quickSiteChromeBackgroundColorTokens.includes(token));
24617
- const defaultSiteChromeBackgroundOverride = { kind: "default" };
24618
- function defaultSiteChromeBackgroundPreferences() {
24619
- return {
24620
- header: defaultSiteChromeBackgroundOverride,
24621
- footer: defaultSiteChromeBackgroundOverride
24622
- };
24623
- }
24624
- function parseSiteChromeBackgroundColorToken(value) {
24625
- const token = parseBackgroundAppearanceColorToken(value);
24626
- return token ? token : null;
24627
- }
24628
- function siteChromeBackgroundPreferencesFromTheme(theme) {
24629
- if (!theme) return defaultSiteChromeBackgroundPreferences();
24630
- return {
24631
- header: theme.header ? siteChromeBackgroundOverrideFromHeader(theme.header) : defaultSiteChromeBackgroundOverride,
24632
- footer: theme.footer ? siteChromeBackgroundOverrideFromFooter(theme.footer) : defaultSiteChromeBackgroundOverride
24633
- };
24634
- }
24635
- function applySiteChromeBackgroundPreferences(theme, preferences) {
24636
- return {
24637
- ...theme,
24638
- header: applyHeaderBackgroundOverride(theme, theme.header, preferences.header),
24639
- footer: applyFooterBackgroundOverride(theme, theme.footer, preferences.footer)
24640
- };
24641
- }
24642
- function canCustomizeSiteChromeBackground(theme, surface$1) {
24643
- if (surface$1 === "footer") return true;
24644
- const header = theme?.header;
24645
- if (!header) return false;
24646
- const navContainerType = header.navContainer?.type ?? "none";
24647
- return header.variant !== "floating" && navContainerType !== "glass" && navContainerType !== "pill";
24648
- }
24649
- function siteChromeBackgroundOverrideLabel(override) {
24650
- switch (override.kind) {
24651
- case "default": return "Default";
24652
- case "color": return formatSiteChromeBackgroundTokenLabel(override.token);
24653
- }
24654
- }
24655
- function formatSiteChromeBackgroundTokenLabel(token) {
24656
- return String(token).replace(/-/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
24657
- }
24658
- function siteChromeBackgroundOverrideEquals(left, right) {
24659
- return left.kind === right.kind && (left.kind === "default" || right.kind === "color" && left.token === right.token);
24660
- }
24661
- function cycleSiteChromeBackgroundOverride(current, direction) {
24662
- const fallback = direction === "next" ? quickSiteChromeBackgroundColorTokens[0] : quickSiteChromeBackgroundColorTokens[quickSiteChromeBackgroundColorTokens.length - 1];
24663
- if (!fallback) return defaultSiteChromeBackgroundOverride;
24664
- if (current.kind === "default") {
24665
- return {
24666
- kind: "color",
24667
- token: fallback
24668
- };
24669
- }
24670
- const currentIndex = quickSiteChromeBackgroundColorTokens.indexOf(current.token);
24671
- if (currentIndex === -1) {
24672
- return {
24673
- kind: "color",
24674
- token: fallback
24675
- };
24676
- }
24677
- const nextIndex = direction === "next" ? (currentIndex + 1) % quickSiteChromeBackgroundColorTokens.length : currentIndex === 0 ? quickSiteChromeBackgroundColorTokens.length - 1 : currentIndex - 1;
24678
- const token = requiredQuickSiteChromeBackgroundColorToken(nextIndex);
24679
- return {
24680
- kind: "color",
24681
- token
24682
- };
24683
- }
24684
- function siteChromeBackgroundOverrideFromHeader(header) {
24685
- return siteChromeBackgroundOverrideFromBackground(header.background);
24686
- }
24687
- function siteChromeBackgroundOverrideFromFooter(footer) {
24688
- return siteChromeBackgroundOverrideFromBackground(footer.background);
24689
- }
24690
- function siteChromeBackgroundOverrideFromBackground(background) {
24691
- if (background?.contentOwner !== siteChromeBackgroundOverrideOwner || background.type !== "color") {
24692
- return defaultSiteChromeBackgroundOverride;
24693
- }
24694
- const token = parseSiteChromeBackgroundColorToken(background.color);
24695
- return token ? {
24696
- kind: "color",
24697
- token
24698
- } : defaultSiteChromeBackgroundOverride;
24699
- }
24700
- function applyHeaderBackgroundOverride(theme, header, override) {
24701
- if (override.kind === "default") return header;
24702
- const background = siteChromeColorBackground(override.token);
24703
- const foreground = resolveChromeForegroundToken(theme, background);
24704
- return {
24705
- ...header,
24706
- background: {
24707
- ...background,
24708
- textColor: foreground
24709
- },
24710
- textColor: foreground,
24711
- navColor: foreground,
24712
- ...header.navLinkStyle ? { navLinkStyle: {
24713
- ...header.navLinkStyle,
24714
- colorToken: foreground
24715
- } } : {}
24716
- };
24717
- }
24718
- function applyFooterBackgroundOverride(theme, footer, override) {
24719
- if (override.kind === "default") return footer;
24720
- const background = siteChromeColorBackground(override.token);
24721
- const foreground = resolveChromeForegroundToken(theme, background);
24722
- return {
24723
- ...footer,
24724
- background: {
24725
- ...background,
24726
- textColor: foreground
24727
- },
24728
- navColor: foreground,
24729
- ...footer.navLinkStyle ? { navLinkStyle: {
24730
- ...footer.navLinkStyle,
24731
- colorToken: foreground
24732
- } } : {},
24733
- ...footer.bottomTextLinkStyle ? { bottomTextLinkStyle: {
24734
- ...footer.bottomTextLinkStyle,
24735
- colorToken: foreground
24736
- } } : {},
24737
- ...footer.bottomBar ? { bottomBar: {
24738
- ...footer.bottomBar,
24739
- textColor: foreground
24740
- } } : {}
24741
- };
24742
- }
24743
- function resolveChromeForegroundToken(theme, background) {
24744
- const resolved = resolveSurfaceForeground(background, expandPalette(theme.palette));
24745
- if (resolved.kind === "unknown") {
24746
- throw new Error(`Site chrome background "${background?.type === "color" ? background.color ?? "unknown" : "unknown"}" has no resolvable AA foreground; the theme palette is incomplete`);
24747
- }
24748
- return resolved.roles.text;
24749
- }
24750
- function siteChromeColorBackground(color) {
24751
- return {
24752
- type: "color",
24753
- contentOwner: siteChromeBackgroundOverrideOwner,
24754
- color
24755
- };
24756
- }
24757
- function toSiteChromeBackgroundColorTokens(tokenNames) {
24758
- return tokenNames.flatMap((name) => {
24759
- const token = parseSiteChromeBackgroundColorToken(name);
24760
- return token ? [token] : [];
24761
- });
24762
- }
24763
- function requiredQuickSiteChromeBackgroundColorToken(index) {
24764
- const token = quickSiteChromeBackgroundColorTokens[index];
24765
- if (!token) {
24766
- throw new Error(`Missing quick site chrome background token at index ${index}`);
24767
- }
24768
- return token;
24769
- }
24770
-
24771
- //#endregion
24772
- //#region ../theme-core/src/site-styles/siteChromeNavPreferences.ts
24773
- const headerNavTypographyPreferenceValues = [
24774
- "inherit",
24775
- "body",
24776
- "heading"
24777
- ];
24778
- const headerNavFontSizePreferenceValues = [
24779
- "inherit",
24780
- "sm",
24781
- "base",
24782
- "lg",
24783
- "xl"
24784
- ];
24785
- const headerNavTypographyOptions = [
24786
- {
24787
- value: "inherit",
24788
- label: "Default"
24789
- },
24790
- {
24791
- value: "body",
24792
- label: "Body"
24793
- },
24794
- {
24795
- value: "heading",
24796
- label: "Heading"
24797
- }
24798
- ];
24799
- const headerNavFontSizeOptions = [
24800
- {
24801
- value: "inherit",
24802
- label: "Default"
24803
- },
24804
- {
24805
- value: "sm",
24806
- label: "Small"
24807
- },
24808
- {
24809
- value: "base",
24810
- label: "Regular"
24811
- },
24812
- {
24813
- value: "lg",
24814
- label: "Large"
24815
- },
24816
- {
24817
- value: "xl",
24818
- label: "Huge"
24819
- }
24820
- ];
24821
- const defaultHeaderNavPreferences = {
24822
- typography: "inherit",
24823
- fontSize: "inherit",
24824
- interactionPalette: "recommended"
24825
- };
24826
- function readHeaderNavPreferences(theme) {
24827
- const typography = theme?.header?.navFontRole;
24828
- const fontSize = theme?.header?.navTextSize;
24829
- const interactionPalette = theme?.header?.interactionPalette ?? "recommended";
24830
- return typography === "body" || typography === "heading" ? {
24831
- typography,
24832
- fontSize: readHeaderNavFontSizePreference(fontSize),
24833
- interactionPalette
24834
- } : {
24835
- ...defaultHeaderNavPreferences,
24836
- fontSize: readHeaderNavFontSizePreference(fontSize),
24837
- interactionPalette
24838
- };
24839
- }
24840
- function headerNavPreferencesEqual(left, right) {
24841
- return left.typography === right.typography && left.fontSize === right.fontSize && left.interactionPalette === right.interactionPalette;
24842
- }
24843
- function withHeaderNavThemePreferences(theme, preferences) {
24844
- const { navFontRole: _navFontRole, navTextSize: _navTextSize, interactionPalette: _interactionPalette, ...headerWithoutPreference } = theme.header;
24845
- const nextHeader = {
24846
- ...headerWithoutPreference,
24847
- ...preferences.typography === "inherit" ? {} : { navFontRole: preferences.typography },
24848
- ...preferences.fontSize === "inherit" ? {} : { navTextSize: preferences.fontSize },
24849
- ...preferences.interactionPalette === "recommended" ? {} : { interactionPalette: preferences.interactionPalette }
24850
- };
24851
- return {
24852
- ...theme,
24853
- header: nextHeader
24854
- };
24855
- }
24856
- /**
24857
- * Applies the user's nav font-role/size overrides on top of a style-resolved
24858
- * nav link style. This is where the precedence contract lands:
24859
- *
24860
- * - The nav typography TREATMENT (case/tracking identity: plain / heading /
24861
- * quiet-caps / brand-caps) is ALWAYS owned by the active header style and is
24862
- * baked into `style` before this call (see `applyNavTypography` in
24863
- * headerLayoutStyles.ts).
24864
- * - Each treatment resolves through a DEFAULT font role: `heading` resolves
24865
- * through the heading role; `plain` / `quiet-caps` / `brand-caps` resolve
24866
- * through the body role.
24867
- * - The user's `navFontRole` (surfaced here as `preferences.typography`) only
24868
- * overrides which font ROLE the treatment resolves through. It never changes
24869
- * the treatment. `'inherit'` keeps the treatment's default role.
24870
- */
24871
- function mergeHeaderNavTypographyPreference(style, preferences) {
24872
- return preferences.typography === "inherit" && preferences.fontSize === "inherit" ? style : {
24873
- ...style,
24874
- ...preferences.typography === "inherit" ? {} : { typography: preferences.typography },
24875
- ...preferences.fontSize === "inherit" ? {} : { textSize: preferences.fontSize }
24876
- };
24877
- }
24878
- function readHeaderNavFontSizePreference(value) {
24879
- return value === "sm" || value === "base" || value === "lg" || value === "xl" ? value : "inherit";
24880
- }
24881
-
24882
25965
  //#endregion
24883
25966
  //#region ../theme-core/src/site-styles/tokenRecipes.ts
24884
25967
  const themeTokenRecipeOptions = {
@@ -24994,24 +26077,24 @@ function normalizeHex$1(value) {
24994
26077
  const blue = triplet.charAt(2);
24995
26078
  return `#${red}${red}${green}${green}${blue}${blue}`;
24996
26079
  }
24997
- function collectDifferences(before, after, path$1, differences) {
26080
+ function collectDifferences(before, after, path$2, differences) {
24998
26081
  if (Object.is(before, after)) return;
24999
26082
  if (Array.isArray(before) && Array.isArray(after)) {
25000
26083
  const sharedLength = Math.min(before.length, after.length);
25001
26084
  for (let index = 0; index < sharedLength; index += 1) {
25002
- collectDifferences(before[index], after[index], `${path$1}[${index}]`, differences);
26085
+ collectDifferences(before[index], after[index], `${path$2}[${index}]`, differences);
25003
26086
  }
25004
26087
  for (let index = sharedLength; index < before.length; index += 1) {
25005
26088
  differences.push({
25006
26089
  kind: "missing",
25007
- path: `${path$1}[${index}]`,
26090
+ path: `${path$2}[${index}]`,
25008
26091
  before: before[index]
25009
26092
  });
25010
26093
  }
25011
26094
  for (let index = sharedLength; index < after.length; index += 1) {
25012
26095
  differences.push({
25013
26096
  kind: "extra",
25014
- path: `${path$1}[${index}]`,
26097
+ path: `${path$2}[${index}]`,
25015
26098
  after: after[index]
25016
26099
  });
25017
26100
  }
@@ -25022,7 +26105,7 @@ function collectDifferences(before, after, path$1, differences) {
25022
26105
  const afterKeys = new Set(Object.keys(after));
25023
26106
  const keys = [...new Set([...beforeKeys, ...afterKeys])].sort();
25024
26107
  for (const key of keys) {
25025
- const childPath = `${path$1}.${key}`;
26108
+ const childPath = `${path$2}.${key}`;
25026
26109
  if (!afterKeys.has(key)) {
25027
26110
  differences.push({
25028
26111
  kind: "missing",
@@ -25045,7 +26128,7 @@ function collectDifferences(before, after, path$1, differences) {
25045
26128
  }
25046
26129
  differences.push({
25047
26130
  kind: "changed",
25048
- path: path$1,
26131
+ path: path$2,
25049
26132
  before,
25050
26133
  after
25051
26134
  });
@@ -25216,8 +26299,8 @@ const sdkConfigureBookingVerticalResponseSchema = sdkCommandApplicationResultSch
25216
26299
  function isRecord$8(value) {
25217
26300
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
25218
26301
  }
25219
- function pullSchemaForRequest(path$1) {
25220
- switch (path$1) {
26302
+ function pullSchemaForRequest(path$2) {
26303
+ switch (path$2) {
25221
26304
  case "/pull/entries/all": return sdkPullEntriesAllResponseSchema;
25222
26305
  case "/pull/entries": return sdkPullEntriesResponseSchema;
25223
26306
  case "/pull/pages": return sdkPullPagesResponseSchema;
@@ -25229,12 +26312,12 @@ function pullSchemaForRequest(path$1) {
25229
26312
  case "/pull/venues": return sdkPullVenuesResponseSchema;
25230
26313
  case "/pull/event-categories": return sdkPullEventCategoriesResponseSchema;
25231
26314
  case "/pull/events": return sdkPullEventsResponseSchema;
25232
- default: return /^\/pull\/entries\/[^/]+$/.test(path$1) ? sdkPullEntriesByTypeResponseSchema : undefined;
26315
+ default: return /^\/pull\/entries\/[^/]+$/.test(path$2) ? sdkPullEntriesByTypeResponseSchema : undefined;
25233
26316
  }
25234
26317
  }
25235
- function rootMutationSchema(method, path$1, body) {
26318
+ function rootMutationSchema(method, path$2, body) {
25236
26319
  if (method === "POST") {
25237
- switch (path$1) {
26320
+ switch (path$2) {
25238
26321
  case "/entries": return isRecord$8(body) && body.publication === "publish" ? sdkUpsertEntryPublishResponseSchema : sdkUpsertEntryDraftResponseSchema;
25239
26322
  case "/pages": return isRecord$8(body) && body.publication === "publish" ? sdkUpsertPagePublishResponseSchema : sdkUpsertPageDraftResponseSchema;
25240
26323
  case "/navigation": return sdkUpsertNavigationMenuResponseSchema;
@@ -25247,11 +26330,11 @@ function rootMutationSchema(method, path$1, body) {
25247
26330
  case "/footer": return sdkUpsertFooterResponseSchema;
25248
26331
  }
25249
26332
  }
25250
- if (method === "PATCH" && path$1 === "/settings") return sdkUpsertSettingsResponseSchema;
25251
- 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;
25252
26335
  return undefined;
25253
26336
  }
25254
- function isDeleteMutationPath(path$1) {
26337
+ function isDeleteMutationPath(path$2) {
25255
26338
  return [
25256
26339
  /^\/entries\/[^/]+\/[^/]+$/,
25257
26340
  /^\/pages\/[^/]+$/,
@@ -25261,35 +26344,42 @@ function isDeleteMutationPath(path$1) {
25261
26344
  /^\/venues\/[^/]+$/,
25262
26345
  /^\/event-categories\/[^/]+$/,
25263
26346
  /^\/events\/[^/]+$/
25264
- ].some((pattern) => pattern.test(path$1));
26347
+ ].some((pattern) => pattern.test(path$2));
25265
26348
  }
25266
- function nestedMutationSchema(method, path$1) {
25267
- if (method === "POST" && /^\/pages\/[^/]+\/blocks$/.test(path$1)) return sdkUpsertBlockResponseSchema;
25268
- if (method === "POST" && /^\/pages\/[^/]+\/blocks\/reorder$/.test(path$1)) return sdkReorderResponseSchema;
25269
- if (method === "PATCH" && /^\/pages\/[^/]+\/blocks\/[^/]+$/.test(path$1)) return sdkPatchPageBlockResponseSchema;
25270
- 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)) {
25271
26354
  return sdkCommandApplicationResultSchema(sdkUpsertEntryDraftResponseSchema.shape.value);
25272
26355
  }
25273
- if (method === "POST" && /^\/pages\/[^/]+\/(publish|unpublish)$/.test(path$1)) {
26356
+ if (method === "POST" && /^\/pages\/[^/]+\/(publish|unpublish)$/.test(path$2)) {
25274
26357
  return sdkCommandApplicationResultSchema(sdkUpsertPageDraftResponseSchema.shape.value);
25275
26358
  }
25276
- if (method === "DELETE" && isDeleteMutationPath(path$1)) return sdkDeleteResponseSchema;
26359
+ if (method === "DELETE" && isDeleteMutationPath(path$2)) return sdkDeleteResponseSchema;
25277
26360
  return undefined;
25278
26361
  }
25279
- function occurrenceResponseSchema(method, path$1) {
26362
+ function occurrenceResponseSchema(method, path$2) {
25280
26363
  const collection = /^\/events\/[^/]+\/runs\/[^/]+\/occurrences$/;
25281
26364
  const member = /^\/events\/[^/]+\/runs\/[^/]+\/occurrences\/[^/]+$/;
25282
- if (method === "GET" && collection.test(path$1)) return sdkListEventOccurrencesResponseSchema;
25283
- if (method === "POST" && collection.test(path$1)) return sdkEventOccurrenceResponseSchema;
25284
- if (method === "PATCH" && member.test(path$1)) return sdkEventOccurrenceResponseSchema;
25285
- if (method === "DELETE" && member.test(path$1)) return sdkDeleteEventOccurrenceResponseSchema;
25286
- 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;
25287
26377
  }
25288
- function schemaForManagementRequest(method, path$1, body) {
26378
+ function schemaForManagementRequest(method, path$2, body) {
25289
26379
  if (method === "GET") {
25290
- return pullSchemaForRequest(path$1) ?? occurrenceResponseSchema(method, path$1);
26380
+ return pullSchemaForRequest(path$2) ?? occurrenceResponseSchema(method, path$2) ?? mediaResponseSchema(method, path$2);
25291
26381
  }
25292
- 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);
25293
26383
  }
25294
26384
  function validateManagementResponse(value, schema) {
25295
26385
  const parsed = schema.safeParse(value);
@@ -25444,10 +26534,10 @@ async function readApiResponse(response, requestInfo, options = {}) {
25444
26534
  }
25445
26535
  return envelope;
25446
26536
  }
25447
- function requestUrl(baseUrl, path$1, params) {
25448
- 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}`;
25449
26539
  const query = new URLSearchParams(params).toString();
25450
- return `${baseUrl}${path$1}?${query}`;
26540
+ return `${baseUrl}${path$2}?${query}`;
25451
26541
  }
25452
26542
  function requestFetchOptions(config, method, body) {
25453
26543
  const options = {
@@ -25461,10 +26551,10 @@ function requestFetchOptions(config, method, body) {
25461
26551
  if (body !== undefined && method !== "GET") options.body = JSON.stringify(body);
25462
26552
  return options;
25463
26553
  }
25464
- async function fetchManagementResponse(config, method, path$1, url, baseUrl, options) {
26554
+ async function fetchManagementResponse(config, method, path$2, url, baseUrl, options) {
25465
26555
  const requestInfo = buildRequestInfo({
25466
26556
  method,
25467
- path: path$1,
26557
+ path: path$2,
25468
26558
  url,
25469
26559
  baseUrl,
25470
26560
  dashboardUrl: config.dashboardUrl,
@@ -25490,12 +26580,12 @@ function throwApiError(response, json, requestInfo) {
25490
26580
  }
25491
26581
  throw new ManagementApiError(`Request failed with status ${response.status}`, "sdk:http-error", undefined, response.status, requestInfo);
25492
26582
  }
25493
- async function requestWithConfig(config, method, path$1, options) {
26583
+ async function requestWithConfig(config, method, path$2, options) {
25494
26584
  const baseUrl = `${config.dashboardUrl}/api/sdk/${config.siteId}`;
25495
- const url = requestUrl(baseUrl, path$1, options?.params);
26585
+ const url = requestUrl(baseUrl, path$2, options?.params);
25496
26586
  const fetchOptions = requestFetchOptions(config, method, options?.body);
25497
- const { response, requestInfo } = await fetchManagementResponse(config, method, path$1, url, baseUrl, fetchOptions);
25498
- 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);
25499
26589
  const json = responseSchema ? await readApiResponse(response, requestInfo, { invalidOkResponse: true }) : await readApiResponse(response, requestInfo, { allowNonJsonOk: true });
25500
26590
  if (!json) return undefined;
25501
26591
  throwApiError(response, json, requestInfo);
@@ -25503,38 +26593,38 @@ async function requestWithConfig(config, method, path$1, options) {
25503
26593
  }
25504
26594
  function createHttpClient(config) {
25505
26595
  return {
25506
- async get(path$1, params) {
25507
- return requestWithConfig(config, "GET", path$1, { params });
26596
+ async get(path$2, params) {
26597
+ return requestWithConfig(config, "GET", path$2, { params });
25508
26598
  },
25509
- async post(path$1, body) {
25510
- return requestWithConfig(config, "POST", path$1, { body });
26599
+ async post(path$2, body) {
26600
+ return requestWithConfig(config, "POST", path$2, { body });
25511
26601
  },
25512
- async patch(path$1, body) {
25513
- return requestWithConfig(config, "PATCH", path$1, { body });
26602
+ async patch(path$2, body) {
26603
+ return requestWithConfig(config, "PATCH", path$2, { body });
25514
26604
  },
25515
- async delete(path$1, body) {
25516
- return requestWithConfig(config, "DELETE", path$1, { body });
26605
+ async delete(path$2, body) {
26606
+ return requestWithConfig(config, "DELETE", path$2, { body });
25517
26607
  },
25518
- async getValidated(path$1, schema, params) {
25519
- return requestWithConfig(config, "GET", path$1, {
26608
+ async getValidated(path$2, schema, params) {
26609
+ return requestWithConfig(config, "GET", path$2, {
25520
26610
  params,
25521
26611
  responseSchema: schema
25522
26612
  });
25523
26613
  },
25524
- async postValidated(path$1, schema, body) {
25525
- return requestWithConfig(config, "POST", path$1, {
26614
+ async postValidated(path$2, schema, body) {
26615
+ return requestWithConfig(config, "POST", path$2, {
25526
26616
  body,
25527
26617
  responseSchema: schema
25528
26618
  });
25529
26619
  },
25530
- async patchValidated(path$1, schema, body) {
25531
- return requestWithConfig(config, "PATCH", path$1, {
26620
+ async patchValidated(path$2, schema, body) {
26621
+ return requestWithConfig(config, "PATCH", path$2, {
25532
26622
  body,
25533
26623
  responseSchema: schema
25534
26624
  });
25535
26625
  },
25536
- async deleteValidated(path$1, schema, body) {
25537
- return requestWithConfig(config, "DELETE", path$1, {
26626
+ async deleteValidated(path$2, schema, body) {
26627
+ return requestWithConfig(config, "DELETE", path$2, {
25538
26628
  body,
25539
26629
  responseSchema: schema
25540
26630
  });
@@ -26149,17 +27239,51 @@ function createIdentifiersOperations(http) {
26149
27239
 
26150
27240
  //#endregion
26151
27241
  //#region ../management-client/src/media.ts
27242
+ const productionMediaOperationsDeps = {
27243
+ createHttpClient,
27244
+ fetch
27245
+ };
26152
27246
  /**
26153
27247
  * Create media operations for the management client.
26154
27248
  * Uses direct-to-storage signed upload URLs for file uploads.
26155
27249
  */
26156
- function createMediaOperations(config) {
27250
+ function createMediaOperations(config, deps = productionMediaOperationsDeps) {
26157
27251
  const baseUrl = `${config.dashboardUrl}/api/sdk/${config.siteId}`;
26158
27252
  const uploadTimeout = config.timeout ?? 10 * 60 * 1e3;
26159
- const http = createHttpClient(config);
27253
+ const http = deps.createHttpClient(config);
26160
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
+ },
26161
27285
  async upload(file) {
26162
- return uploadMediaFile(config, http, baseUrl, uploadTimeout, file);
27286
+ return uploadMediaFile(config, http, baseUrl, uploadTimeout, file, deps.fetch);
26163
27287
  },
26164
27288
  async exists(storagePath) {
26165
27289
  const result = await http.post("/media/exists", { storagePath });
@@ -26174,7 +27298,48 @@ function createMediaOperations(config) {
26174
27298
  }
26175
27299
  };
26176
27300
  }
26177
- 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) {
26178
27343
  const filename = file.filename || "upload";
26179
27344
  const mimeType = file.contentType || "application/octet-stream";
26180
27345
  const { signedUrl, storagePath, bucket } = await http.post("/media/upload-url", {
@@ -26192,7 +27357,7 @@ async function uploadMediaFile(config, http, baseUrl, uploadTimeout, file) {
26192
27357
  dashboardUrl: config.dashboardUrl,
26193
27358
  siteId: config.siteId
26194
27359
  });
26195
- const response = await putMediaFile(signedUrl, file.data, mimeType, uploadTimeout, requestInfo);
27360
+ const response = await putMediaFile(signedUrl, file.data, mimeType, uploadTimeout, requestInfo, fetchImpl);
26196
27361
  if (!response.ok && response.status !== 409) {
26197
27362
  throw new ManagementApiError(`Upload failed with status ${response.status}`, "sdk:http-error", undefined, response.status, requestInfo);
26198
27363
  }
@@ -26206,9 +27371,9 @@ async function uploadMediaFile(config, http, baseUrl, uploadTimeout, file) {
26206
27371
  identifier: file.identifier
26207
27372
  });
26208
27373
  }
26209
- async function putMediaFile(signedUrl, data, mimeType, timeout, requestInfo) {
27374
+ async function putMediaFile(signedUrl, data, mimeType, timeout, requestInfo, fetchImpl) {
26210
27375
  try {
26211
- return await fetch(signedUrl, {
27376
+ return await fetchImpl(signedUrl, {
26212
27377
  method: "PUT",
26213
27378
  headers: { "Content-Type": mimeType },
26214
27379
  body: new Uint8Array(data),
@@ -26951,8 +28116,8 @@ async function withRiverbankEnv(target, run) {
26951
28116
  }
26952
28117
  const previous = envScopeQueue;
26953
28118
  let release;
26954
- envScopeQueue = new Promise((resolve$1) => {
26955
- release = resolve$1;
28119
+ envScopeQueue = new Promise((resolve$2) => {
28120
+ release = resolve$2;
26956
28121
  });
26957
28122
  await previous;
26958
28123
  const previousEnv = process.env.RIVERBANK_ENV;
@@ -27134,7 +28299,7 @@ function shouldConfirm(plan, prepared) {
27134
28299
  return readyTargets(prepared).length > 0 && !hasPreparationBarrierFailure(plan, prepared);
27135
28300
  }
27136
28301
  function hasPreparationBarrierFailure(plan, prepared) {
27137
- 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");
27138
28303
  }
27139
28304
  async function confirmPrepared(plan, prepared, deps) {
27140
28305
  if (!shouldConfirm(plan, prepared)) return true;
@@ -27442,7 +28607,7 @@ function formatDateShort(isoDate) {
27442
28607
  */
27443
28608
  async function parseJsonData(options) {
27444
28609
  if (options.file) {
27445
- const filePath = path.resolve(options.file);
28610
+ const filePath = path$1.resolve(options.file);
27446
28611
  const content = await fs.readFile(filePath, "utf-8");
27447
28612
  return JSON.parse(content);
27448
28613
  }
@@ -27496,12 +28661,12 @@ async function confirmAction(message, options, isRemote) {
27496
28661
  input: process.stdin,
27497
28662
  output: process.stderr
27498
28663
  });
27499
- return new Promise((resolve$1) => {
28664
+ return new Promise((resolve$2) => {
27500
28665
  let settled = false;
27501
28666
  const finish = (confirmed) => {
27502
28667
  if (settled) return;
27503
28668
  settled = true;
27504
- resolve$1(confirmed);
28669
+ resolve$2(confirmed);
27505
28670
  };
27506
28671
  rl.once("close", () => finish(false));
27507
28672
  rl.question(`${message} (y/N): `, (answer) => {
@@ -30624,19 +31789,19 @@ var require_react_development = /* @__PURE__ */ __commonJSMin(((exports, module)
30624
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. ");
30625
31790
  actScopeDepth = prevActScopeDepth;
30626
31791
  }
30627
- function recursivelyFlushAsyncActWork(returnValue, resolve$1, reject) {
31792
+ function recursivelyFlushAsyncActWork(returnValue, resolve$2, reject) {
30628
31793
  var queue = ReactSharedInternals$1.actQueue;
30629
31794
  if (null !== queue) if (0 !== queue.length) try {
30630
31795
  flushActQueue(queue);
30631
31796
  enqueueTask(function() {
30632
- return recursivelyFlushAsyncActWork(returnValue, resolve$1, reject);
31797
+ return recursivelyFlushAsyncActWork(returnValue, resolve$2, reject);
30633
31798
  });
30634
31799
  return;
30635
31800
  } catch (error) {
30636
31801
  ReactSharedInternals$1.thrownErrors.push(error);
30637
31802
  }
30638
31803
  else ReactSharedInternals$1.actQueue = null;
30639
- 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);
30640
31805
  }
30641
31806
  function flushActQueue(queue) {
30642
31807
  if (!isFlushing) {
@@ -30797,14 +31962,14 @@ var require_react_development = /* @__PURE__ */ __commonJSMin(((exports, module)
30797
31962
  queueSeveralMicrotasks(function() {
30798
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 () => ...);"));
30799
31964
  });
30800
- return { then: function(resolve$1, reject) {
31965
+ return { then: function(resolve$2, reject) {
30801
31966
  didAwaitActCall = !0;
30802
31967
  thenable.then(function(returnValue) {
30803
31968
  popActScope(prevActQueue, prevActScopeDepth);
30804
31969
  if (0 === prevActScopeDepth) {
30805
31970
  try {
30806
31971
  flushActQueue(queue), enqueueTask(function() {
30807
- return recursivelyFlushAsyncActWork(returnValue, resolve$1, reject);
31972
+ return recursivelyFlushAsyncActWork(returnValue, resolve$2, reject);
30808
31973
  });
30809
31974
  } catch (error$0) {
30810
31975
  ReactSharedInternals$1.thrownErrors.push(error$0);
@@ -30814,7 +31979,7 @@ var require_react_development = /* @__PURE__ */ __commonJSMin(((exports, module)
30814
31979
  ReactSharedInternals$1.thrownErrors.length = 0;
30815
31980
  reject(_thrownError);
30816
31981
  }
30817
- } else resolve$1(returnValue);
31982
+ } else resolve$2(returnValue);
30818
31983
  }, function(error) {
30819
31984
  popActScope(prevActQueue, prevActScopeDepth);
30820
31985
  0 < ReactSharedInternals$1.thrownErrors.length ? (error = aggregateErrors(ReactSharedInternals$1.thrownErrors), ReactSharedInternals$1.thrownErrors.length = 0, reject(error)) : reject(error);
@@ -30827,11 +31992,11 @@ var require_react_development = /* @__PURE__ */ __commonJSMin(((exports, module)
30827
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(() => ...)"));
30828
31993
  }), ReactSharedInternals$1.actQueue = null);
30829
31994
  if (0 < ReactSharedInternals$1.thrownErrors.length) throw callback = aggregateErrors(ReactSharedInternals$1.thrownErrors), ReactSharedInternals$1.thrownErrors.length = 0, callback;
30830
- return { then: function(resolve$1, reject) {
31995
+ return { then: function(resolve$2, reject) {
30831
31996
  didAwaitActCall = !0;
30832
31997
  0 === prevActScopeDepth ? (ReactSharedInternals$1.actQueue = queue, enqueueTask(function() {
30833
- return recursivelyFlushAsyncActWork(returnValue$jscomp$0, resolve$1, reject);
30834
- })) : resolve$1(returnValue$jscomp$0);
31998
+ return recursivelyFlushAsyncActWork(returnValue$jscomp$0, resolve$2, reject);
31999
+ })) : resolve$2(returnValue$jscomp$0);
30835
32000
  } };
30836
32001
  };
30837
32002
  exports.cache = function(fn) {
@@ -33777,13 +34942,13 @@ function optionalRef(ref, field) {
33777
34942
  field
33778
34943
  }] : [];
33779
34944
  }
33780
- function collectVenueRefsFromIntervals(intervals, path$1) {
33781
- 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`));
33782
34947
  }
33783
- function collectNavigationRefs(items, path$1) {
34948
+ function collectNavigationRefs(items, path$2) {
33784
34949
  return items.flatMap((item, index) => {
33785
34950
  if (!isRecord$5(item)) return [];
33786
- const itemPath = `${path$1}[${index}]`;
34951
+ const itemPath = `${path$2}[${index}]`;
33787
34952
  const refs = [];
33788
34953
  if (typeof item.pageRef === "string") {
33789
34954
  refs.push({
@@ -33803,26 +34968,26 @@ function collectNavigationRefs(items, path$1) {
33803
34968
  return refs;
33804
34969
  });
33805
34970
  }
33806
- function collectBlockRefs(blocks, path$1) {
34971
+ function collectBlockRefs(blocks, path$2) {
33807
34972
  return blocks.flatMap((block, index) => {
33808
34973
  if (!isRecord$5(block)) return [];
33809
- const blockPath = `${path$1}[${index}]`;
34974
+ const blockPath = `${path$2}[${index}]`;
33810
34975
  return [...collectNestedRefs(block.props, `${blockPath}.props`), ...collectNestedRefs(block.children, `${blockPath}.children`)];
33811
34976
  });
33812
34977
  }
33813
- function collectNestedRefs(value, path$1) {
34978
+ function collectNestedRefs(value, path$2) {
33814
34979
  if (typeof value === "string") {
33815
34980
  const parsed = parseResourceRef(value);
33816
34981
  return parsed.ok ? [{
33817
34982
  ref: value,
33818
- field: path$1
34983
+ field: path$2
33819
34984
  }] : [];
33820
34985
  }
33821
34986
  if (Array.isArray(value)) {
33822
- return value.flatMap((item, index) => collectNestedRefs(item, `${path$1}[${index}]`));
34987
+ return value.flatMap((item, index) => collectNestedRefs(item, `${path$2}[${index}]`));
33823
34988
  }
33824
34989
  if (!isRecord$5(value)) return [];
33825
- 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}`));
33826
34991
  }
33827
34992
 
33828
34993
  //#endregion
@@ -34088,8 +35253,8 @@ function invalidPayloadError(input, issue) {
34088
35253
  message: `${field}: ${issue.message}`
34089
35254
  };
34090
35255
  }
34091
- function commandIndexForIssuePath(path$1) {
34092
- 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;
34093
35258
  }
34094
35259
  function commandTypeForIssue(input, commandIndex) {
34095
35260
  if (!isRecord$5(input) || !isUnknownArray(input.commands)) return null;
@@ -34099,9 +35264,9 @@ function commandTypeForIssue(input, commandIndex) {
34099
35264
  function isUnknownArray(value) {
34100
35265
  return Array.isArray(value);
34101
35266
  }
34102
- function formatIssuePath(path$1) {
34103
- if (path$1.length === 0) return "<root>";
34104
- 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) => {
34105
35270
  if (typeof segment === "number") return `${formatted}[${segment}]`;
34106
35271
  if (formatted.length === 0) return String(segment);
34107
35272
  return `${formatted}.${String(segment)}`;
@@ -34834,6 +35999,8 @@ const FILE_SIZE_LIMITS = {
34834
35999
  instagramZip: unsafeAsPositiveByteCount(100 * 1024 * 1024),
34835
36000
  bookingEvidence: unsafeAsPositiveByteCount(25 * 1024 * 1024)
34836
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);
34837
36004
  const DEFAULT_PRIVATE_FILE_SIGNED_URL_EXPIRY_SECONDS = unsafeAsPositiveSeconds(5 * 60);
34838
36005
  const MAX_SAFE_DOWNLOAD_FILENAME_LENGTH = 180;
34839
36006
  function asPositiveByteCount(value) {
@@ -35097,6 +36264,16 @@ function classifyKnownMimeType(mimeType) {
35097
36264
  default: return err("unsupported-mime-type");
35098
36265
  }
35099
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
+ }
35100
36277
  function isMimeTypeAllowedForPolicy(mimeType, policy) {
35101
36278
  const kindResult = classifyKnownMimeType(mimeType);
35102
36279
  return kindResult.ok && isFileKindAllowed(kindResult.value, policy);
@@ -35664,9 +36841,9 @@ function buildDurableFileStoragePath(input) {
35664
36841
  input.fileAssetId,
35665
36842
  input.safeDownloadFilename
35666
36843
  ];
35667
- const path$1 = joinStoragePathSegments(segments);
35668
- if (!path$1.ok) return path$1;
35669
- 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));
35670
36847
  }
35671
36848
  function parseStoragePath(value) {
35672
36849
  return joinStoragePathSegments(value.split("/"));
@@ -37091,10 +38268,10 @@ function emptyStatePatch(patch) {
37091
38268
  rejection_reason: patch.rejection_reason ?? null
37092
38269
  };
37093
38270
  }
37094
- function traverse(items, parse) {
38271
+ function traverse(items, parse$1) {
37095
38272
  const values = [];
37096
38273
  for (const item of items) {
37097
- const parsed = parse(item);
38274
+ const parsed = parse$1(item);
37098
38275
  if (!parsed.ok) return parsed;
37099
38276
  values.push(parsed.value);
37100
38277
  }
@@ -37775,18 +38952,18 @@ function resolveFileDownload(input) {
37775
38952
  assetKind: input.asset.kind
37776
38953
  });
37777
38954
  }
37778
- const access = authorizeFileAccess(input.actor, input.asset.accessPolicy);
37779
- if (!access.ok) {
38955
+ const access$1 = authorizeFileAccess(input.actor, input.asset.accessPolicy);
38956
+ if (!access$1.ok) {
37780
38957
  return err({
37781
38958
  tag: "access-denied",
37782
- reason: access.error
38959
+ reason: access$1.error
37783
38960
  });
37784
38961
  }
37785
38962
  return ok(fileAccessGrantToDownloadPlan({
37786
38963
  asset: input.asset,
37787
38964
  requestedUse: input.requestedUse,
37788
38965
  disposition: input.disposition ?? defaultDispositionForFileUse(input.requestedUse),
37789
- grant: access.value
38966
+ grant: access$1.value
37790
38967
  }));
37791
38968
  }
37792
38969
  function fileAccessGrantToDownloadPlan(input) {
@@ -38317,9 +39494,9 @@ const EXTENSIONS_BY_MIME_TYPE = {
38317
39494
  };
38318
39495
  function fileExtension(filename) {
38319
39496
  const pathSegments = filename.replace(/\\/g, "/").split("/");
38320
- const basename = pathSegments[pathSegments.length - 1] ?? filename;
38321
- const dotIndex = basename.lastIndexOf(".");
38322
- 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();
38323
39500
  }
38324
39501
  function getMimeType(filename) {
38325
39502
  return MIME_TYPES_BY_EXTENSION[fileExtension(filename)] ?? "application/octet-stream";
@@ -38867,20 +40044,20 @@ function formatMediaUsageStage(stage) {
38867
40044
  function mediaUsageLocationLabel(location$1) {
38868
40045
  switch (location$1.kind) {
38869
40046
  case "pageBlock": {
38870
- const path$1 = location$1.pageDraftPath ?? location$1.pagePublishedPath ?? null;
40047
+ const path$2 = location$1.pageDraftPath ?? location$1.pagePublishedPath ?? null;
38871
40048
  const title = location$1.pageTitle ?? "Untitled page";
38872
- const where = path$1 ? `${title} (${path$1})` : title;
40049
+ const where = path$2 ? `${title} (${path$2})` : title;
38873
40050
  return `${where} — ${formatMediaUsageStage(location$1.stage)} — Block: ${location$1.blockKind}`;
38874
40051
  }
38875
40052
  case "entrySlotBlock": {
38876
- const path$1 = location$1.routeDraftPath ?? location$1.routePublishedPath ?? null;
38877
- 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;
38878
40055
  return `${where} — Page Content: ${location$1.slotKey} — ${formatMediaUsageStage(location$1.stage)} — Block: ${location$1.blockKind}`;
38879
40056
  }
38880
40057
  case "surfaceSlotBlock": return `Page surface ${location$1.pageSurfaceId} — Page Content: ${location$1.slotKey} — ${formatMediaUsageStage(location$1.stage)} — Block: ${location$1.blockKind}`;
38881
40058
  case "contentEntry": {
38882
- const path$1 = location$1.routeDraftPath ?? location$1.routePublishedPath ?? null;
38883
- 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;
38884
40061
  return `${where} — ${formatMediaUsageStage(location$1.stage)}`;
38885
40062
  }
38886
40063
  case "productFileDownload": return `${location$1.productTitle} — Product download`;
@@ -38910,10 +40087,17 @@ function hasMediaUsage(response) {
38910
40087
  return response.items.some((item) => item.usedCount > 0);
38911
40088
  }
38912
40089
  function planBulkDeleteCompletionToast(result) {
40090
+ const hasWarnings = (result.warnings?.length ?? 0) > 0;
38913
40091
  if (result.failed > 0) {
38914
40092
  return {
38915
40093
  kind: "error",
38916
- 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.`
38917
40101
  };
38918
40102
  }
38919
40103
  return {
@@ -39178,7 +40362,7 @@ const ORDER = [
39178
40362
  ];
39179
40363
  const getImageUrl = (asset, params = {}, basePath = DEFAULT_BASE_PATH) => {
39180
40364
  const trimmedBase = basePath.endsWith("/") ? basePath.slice(0, Math.max(basePath.length - 1, 0)) : basePath;
39181
- const path$1 = `${trimmedBase}/${encodeURIComponent(asset.id)}`;
40365
+ const path$2 = `${trimmedBase}/${encodeURIComponent(asset.id)}`;
39182
40366
  const searchParams = new URLSearchParams();
39183
40367
  if (params.w !== undefined) searchParams.set("w", formatNumber(params.w));
39184
40368
  if (params.h !== undefined) searchParams.set("h", formatNumber(params.h));
@@ -39197,7 +40381,7 @@ const getImageUrl = (asset, params = {}, basePath = DEFAULT_BASE_PATH) => {
39197
40381
  if (value !== null) sorted.set(key, value);
39198
40382
  });
39199
40383
  const query = sorted.toString();
39200
- return query ? `${path$1}?${query}` : path$1;
40384
+ return query ? `${path$2}?${query}` : path$2;
39201
40385
  };
39202
40386
 
39203
40387
  //#endregion
@@ -39967,8 +41151,8 @@ function mediaIdentifierOrFallback(current, fallback) {
39967
41151
  function isRecord$4(value) {
39968
41152
  return typeof value === "object" && value !== null && !Array.isArray(value);
39969
41153
  }
39970
- function formatContentFieldPath(path$1) {
39971
- 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("");
39972
41156
  }
39973
41157
  function summarizeContentMediaValue(value) {
39974
41158
  if (typeof value === "string") {
@@ -39981,7 +41165,7 @@ function summarizeContentMediaValue(value) {
39981
41165
  if (isRecord$4(value)) return `object(${Object.keys(value).sort().join(",")})`;
39982
41166
  return typeof value;
39983
41167
  }
39984
- function auditMediaValue(context, path$1, allowedMediaTypes$1, value) {
41168
+ function auditMediaValue(context, path$2, allowedMediaTypes$1, value) {
39985
41169
  const expectedMediaType = allowedMediaTypes$1[0];
39986
41170
  if (value === undefined || value === null) return [];
39987
41171
  if (typeof value === "string") {
@@ -39990,7 +41174,7 @@ function auditMediaValue(context, path$1, allowedMediaTypes$1, value) {
39990
41174
  return [{
39991
41175
  tag: "blank-string",
39992
41176
  context,
39993
- path: path$1,
41177
+ path: path$2,
39994
41178
  valueSummary: summarizeContentMediaValue(value),
39995
41179
  expectedMediaType
39996
41180
  }];
@@ -39998,7 +41182,7 @@ function auditMediaValue(context, path$1, allowedMediaTypes$1, value) {
39998
41182
  return [{
39999
41183
  tag: "legacy-string",
40000
41184
  context,
40001
- path: path$1,
41185
+ path: path$2,
40002
41186
  valueSummary: summarizeContentMediaValue(value),
40003
41187
  originalValue: value,
40004
41188
  expectedMediaType,
@@ -40014,7 +41198,7 @@ function auditMediaValue(context, path$1, allowedMediaTypes$1, value) {
40014
41198
  return [{
40015
41199
  tag: "malformed-object",
40016
41200
  context,
40017
- path: path$1,
41201
+ path: path$2,
40018
41202
  valueSummary: summarizeContentMediaValue(value),
40019
41203
  expectedMediaType
40020
41204
  }];
@@ -40417,8 +41601,8 @@ function mediaTypeFromAsset(row) {
40417
41601
  }
40418
41602
  function legacyStorageValues(siteId, storagePath) {
40419
41603
  const sitePrefix = `sites/${siteId}/`;
40420
- const relative = storagePath.startsWith(sitePrefix) ? storagePath.slice(sitePrefix.length) : storagePath;
40421
- return [...new Set([storagePath, `/${relative}`])];
41604
+ const relative$1 = storagePath.startsWith(sitePrefix) ? storagePath.slice(sitePrefix.length) : storagePath;
41605
+ return [...new Set([storagePath, `/${relative$1}`])];
40422
41606
  }
40423
41607
  function assetCandidates(rows) {
40424
41608
  return rows.flatMap((row) => {
@@ -40806,10 +41990,10 @@ var BrowserImage = class extends ImageBase {
40806
41990
  return Promise.reject(new Error(`Cannot load buffer as an image in browser`));
40807
41991
  }
40808
41992
  this.image = img;
40809
- return new Promise((resolve$1, reject) => {
41993
+ return new Promise((resolve$2, reject) => {
40810
41994
  const onImageLoad = () => {
40811
41995
  this._initCanvas();
40812
- resolve$1(this);
41996
+ resolve$2(this);
40813
41997
  };
40814
41998
  if (img.complete) {
40815
41999
  onImageLoad();
@@ -42395,7 +43579,7 @@ const isMediaValue = (value) => isRecordValue(value) && !("attrs" in value) && t
42395
43579
  //#endregion
42396
43580
  //#region src/content/media-manifest.ts
42397
43581
  const MANIFEST_VERSION = 2;
42398
- const getManifestPath = (contentDir) => path.join(contentDir, "media", ".manifest.json");
43582
+ const getManifestPath = (contentDir) => path$1.join(contentDir, "media", ".manifest.json");
42399
43583
  const ensureDir = async (dirPath) => {
42400
43584
  try {
42401
43585
  await fs.mkdir(dirPath, { recursive: true });
@@ -42487,7 +43671,7 @@ async function readMediaManifest(contentDir) {
42487
43671
  return createEmptyMediaManifest();
42488
43672
  }
42489
43673
  async function writeMediaManifest(contentDir, manifest) {
42490
- const mediaDir = path.join(contentDir, "media");
43674
+ const mediaDir = path$1.join(contentDir, "media");
42491
43675
  await ensureDir(mediaDir);
42492
43676
  const updatedManifest = {
42493
43677
  ...manifest,
@@ -42623,7 +43807,7 @@ const DEFAULT_RETRY_CONFIG = {
42623
43807
  * Sleep for a given number of milliseconds.
42624
43808
  */
42625
43809
  function sleep(ms) {
42626
- return new Promise((resolve$1) => setTimeout(resolve$1, ms));
43810
+ return new Promise((resolve$2) => setTimeout(resolve$2, ms));
42627
43811
  }
42628
43812
  /**
42629
43813
  * Calculate exponential backoff delay with jitter.
@@ -42732,8 +43916,8 @@ const TRANSFORMABLE_EXTENSIONS = [
42732
43916
  * Check if a file path is an image that can be transformed by Supabase.
42733
43917
  * @param path - The file path or storage path
42734
43918
  */
42735
- function isTransformableImage(path$1) {
42736
- const ext = path$1.split(".").pop()?.toLowerCase();
43919
+ function isTransformableImage(path$2) {
43920
+ const ext = path$2.split(".").pop()?.toLowerCase();
42737
43921
  return TRANSFORMABLE_EXTENSIONS.includes(ext ?? "");
42738
43922
  }
42739
43923
  /**
@@ -43044,7 +44228,7 @@ function collectRichTextImageAttrs(value, out = [], location$1 = "") {
43044
44228
  //#endregion
43045
44229
  //#region src/media/pull-manifest.ts
43046
44230
  const buildLocalFilename = (identifier, asset) => {
43047
- const extensionFromFilename = path.extname(asset.filename ?? "");
44231
+ const extensionFromFilename = path$1.extname(asset.filename ?? "");
43048
44232
  const extension = extensionFromFilename || getExtensionForMimeType(asset.mimeType) || "";
43049
44233
  return extension ? `${identifier}${extension}` : identifier;
43050
44234
  };
@@ -43366,6 +44550,165 @@ function sha256(buffer) {
43366
44550
  return createHash("sha256").update(buffer).digest("hex");
43367
44551
  }
43368
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
+
43369
44712
  //#endregion
43370
44713
  //#region src/media/local-media.ts
43371
44714
  const MEDIA_DIR_NAME = "media";
@@ -43387,10 +44730,10 @@ const listMediaDirFiles = async (mediaDir) => {
43387
44730
  }
43388
44731
  };
43389
44732
  const findMatchingFilename = async (contentDir, identifier, manifest) => {
43390
- const mediaDir = path.join(contentDir, MEDIA_DIR_NAME);
44733
+ const mediaDir = path$1.join(contentDir, MEDIA_DIR_NAME);
43391
44734
  const manifestEntry = manifest.assets[identifier];
43392
44735
  if (manifestEntry?.filename) {
43393
- const candidatePath = path.join(mediaDir, manifestEntry.filename);
44736
+ const candidatePath = path$1.join(mediaDir, manifestEntry.filename);
43394
44737
  if (await fileExists(candidatePath)) {
43395
44738
  return manifestEntry.filename;
43396
44739
  }
@@ -43398,7 +44741,7 @@ const findMatchingFilename = async (contentDir, identifier, manifest) => {
43398
44741
  const files = await listMediaDirFiles(mediaDir);
43399
44742
  const matches = files.filter((name) => {
43400
44743
  if (name.startsWith(".")) return false;
43401
- const parsed = path.parse(name);
44744
+ const parsed = path$1.parse(name);
43402
44745
  return parsed.name === identifier;
43403
44746
  });
43404
44747
  if (matches.length > 1) {
@@ -43409,9 +44752,13 @@ const findMatchingFilename = async (contentDir, identifier, manifest) => {
43409
44752
  async function loadLocalMediaFile(contentDir, identifier, manifest) {
43410
44753
  const filename = await findMatchingFilename(contentDir, identifier, manifest);
43411
44754
  if (!filename) return null;
43412
- const mediaDir = path.join(contentDir, MEDIA_DIR_NAME);
43413
- const filePath = path.join(mediaDir, filename);
43414
- 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;
43415
44762
  const checksum = sha256(data);
43416
44763
  const mimeType = getMimeType(filename);
43417
44764
  manifest.assets[identifier] = {
@@ -43430,15 +44777,31 @@ async function loadLocalMediaFile(contentDir, identifier, manifest) {
43430
44777
  };
43431
44778
  }
43432
44779
  async function writeLocalMediaFile(contentDir, filename, data) {
43433
- const mediaDir = path.join(contentDir, MEDIA_DIR_NAME);
44780
+ const mediaDir = path$1.join(contentDir, MEDIA_DIR_NAME);
43434
44781
  await fs.mkdir(mediaDir, { recursive: true });
43435
- const filePath = path.join(mediaDir, filename);
43436
- await fs.writeFile(filePath, data);
43437
- 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
+ };
43438
44798
  }
43439
- async function readLocalMediaChecksum(filePath) {
43440
- const data = await fs.readFile(filePath);
43441
- 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);
43442
44805
  }
43443
44806
  async function localMediaFileExists(filePath) {
43444
44807
  return fileExists(filePath);
@@ -43503,7 +44866,7 @@ const parseFilenameFromUrl = (src) => {
43503
44866
  }
43504
44867
  };
43505
44868
  const ensureFilenameExtension = (filename, mimeType) => {
43506
- if (path.extname(filename)) return filename;
44869
+ if (path$1.extname(filename)) return filename;
43507
44870
  const ext = getExtensionForMimeType(mimeType);
43508
44871
  return ext ? `${filename}${ext}` : filename;
43509
44872
  };
@@ -44680,7 +46043,7 @@ const removeStaleLocalFile = async (options) => {
44680
46043
  if (options.dryRun || !options.previousFilename || options.previousFilename === options.filename) {
44681
46044
  return;
44682
46045
  }
44683
- const previousPath = path.join(options.contentDir, "media", options.previousFilename);
46046
+ const previousPath = path$1.join(options.contentDir, "media", options.previousFilename);
44684
46047
  if (await localMediaFileExists(previousPath)) {
44685
46048
  await fs.unlink(previousPath).catch(() => {});
44686
46049
  }
@@ -44711,7 +46074,8 @@ const applyDownloadDecision = async (decision, context) => {
44711
46074
  context.warnings.push(`Failed to download media for "${decision.identifier}" from ${urlResult.value}.`);
44712
46075
  return;
44713
46076
  }
44714
- 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));
44715
46079
  context.manifest.assets[decision.identifier] = {
44716
46080
  checksum: sha256(downloaded.data),
44717
46081
  filename: decision.filename,
@@ -44728,7 +46092,7 @@ const syncPullMediaDownload = async (planned, indexes, context) => {
44728
46092
  return;
44729
46093
  }
44730
46094
  const filename = buildLocalFilename(identifier, asset);
44731
- const localFilePath = path.join(context.contentDir, "media", filename);
46095
+ const localFilePath = path$1.join(context.contentDir, "media", filename);
44732
46096
  const existingEntry = context.manifest.assets[identifier];
44733
46097
  await removeStaleLocalFile({
44734
46098
  contentDir: context.contentDir,
@@ -44766,10 +46130,16 @@ const syncPullMediaDownloads = async (options) => {
44766
46130
  }
44767
46131
  if (!options.dryRun) options.summary.pendingDownloads = 0;
44768
46132
  };
44769
- const sha256FromFile = async (filePath) => {
44770
- const buffer = await fs.readFile(filePath);
44771
- return sha256(buffer);
44772
- };
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
+ }
44773
46143
 
44774
46144
  //#endregion
44775
46145
  //#region src/media/pull-workflow.ts
@@ -45189,4 +46559,4 @@ Examples:
45189
46559
  }
45190
46560
 
45191
46561
  //#endregion
45192
- export { pagePathForCommand as $, NAV_LINK_BORDER_RADIUS_MAP as $n, DEFAULT_SURFACE_TEXT_COLOR as $r, loadSdkEnvironment as $t, getDirectImageUrlForRelativePath as A, sdkConfiguredBookingVerticalConfigSchema as Ai, MQ_MD_DOWN as An, resolveContextualButtonRole as Ar, mapEntryForOutput as At, sortRecord as B, eventListTreatmentValues as Bn, textColorStyle as Br, executeEnvironmentPlan as Bt, deleteEnvSpecificKeys as C, internalLinkSchema as Ci, getHeaderPaintTargets as Cn, generateButtonCss as Cr, createListCommand$1 as Ct, isExplicitRectCrop as D, isResolvedInternalLinkEntityType as Di, BREAKPOINT_SM as Dn, contextualButtonSurfaceResetStyles as Dr, getOutputContext as Dt, normalizeWidthCandidates as E, isInternalResolvedLinkValue as Ei, BREAKPOINT_MD as En, resolvedButtonFocusDeclarations as Er, formatDateShort as Et, resolveVideoSelectionIntentForMediaField as F, pageDesignEditorBackgroundOverrideOwner as Fn, backgroundColorStyle as Fr, runWithInvocation as Ft, evaluateSiteCommandBatchExposure as G, offeringsPreviewCompositionTreatmentValues as Gn, generateInteractiveChildInheritanceCSS as Gr, withRiverbankEnv as Gt, createDryRunReport as H, featureCardCompositionTreatmentValues as Hn, generateDefaultButtonSystem as Hr, planEnvironmentExecution as Ht, asRecord as I, assertNever$5 as In, borderColorStyle as Ir, withCommandContext as It, blockIdentifierForCommand as J, visibleHeadingDecorationFamilyValues as Jn, themeButtonClassName as Jr, getEnvPrefix as Jt, evaluateSiteCommandExposure as K, testimonialsCompositionTreatmentValues as Kn, TokenResolver as Kr, formatError as Kt, parseRelativeStoragePath as L, defineBlockDesignCapability as Ln, cssVarValue as Lr, withConfirmation as Lt, getImageUrl as M, MQ_SM_UP as Mn, resolveOnColorForThemeToken as Mr, parseJsonData as Mt, isSiteScopedDirectImageStoragePath as N, MQ_TABLET_ONLY as Nn, resolveThemeColorReferenceValue as Nr, parsePaginationOptions as Nt, parseAspectRatioValue as O, linkSchema as Oi, BREAKPOINT_XL as On, contextualButtonSurfaceStyles as Or, getOutputOnlyContext as Ot, deriveVideoSlotPolicy as P, headerVariantValues as Pn, resolveTokenOrCssColorValue as Pr, resolveCommandPath as Pt, pageIdentifierForCommand as Q, parseCustomCssRules as Qn, generateShadowValue$1 as Qr, isRemoteConfigured as Qt, classifyKnownMimeType as R, defineFrameCapability as Rn, mergeStyles as Rr, withErrorHandling as Rt, walkAndCollectImageAttrs as S, externalLinkSchema$1 as Si, generateMobileOverlayBackdropFilterFix as Sn, AFFORDANCE_SUPPRESS_CLASS as Sr, createGetCommand as St, rotateNormalizedPoint as T, internalRouteOnlyLinkSchema as Ti, BREAKPOINT_LG as Tn, resolvedButtonColourDeclarations as Tr, currentInvocationEnvelope as Tt, parseAndPlanCommandBatchForTypes as U, headingDecorationColorTokenValues as Un, generateEffectsCSS as Ur, planEnvironmentWriteAuthorization as Ut, stableStringify as V, eyebrowTreatmentValues as Vn, resolveSurfaceForeground as Vr, parseEnvironmentSelection as Vt, mapEventCommandContent as W, introCompositionTreatmentValues as Wn, generateBaseInteractiveCSS as Wr, requireEnvironmentWriteAuthorization as Wt, entryIdentifierForCommand as X, getComponentShadowVar as Xn, compileButtonColourStates as Xr, getEnvironmentDebugInfo as Xt, blockOrderIdentifiersForCommand as Y, getComponentBorderWidthPx as Yn, parseThemeForPersistence as Yr, getEnvVarName as Yt, pageIdentifierForBlockCommand as Z, parseBlockCustomCssRules as Zn, formatButtonColourContractError as Zr, isLocalConfigured as Zt, hasOwnSettingsField as _, persistableMediaSchema as _i, sdkThemeWireSchema as _n, generateShadowValue as _r, refSlug as _t, syncLocalMediaForPull as a, onColorTokenName as ai, getCuratedSiteStyle as an, INPUT_FONT_SIZE_MAP as ar, createBlockRef as at, attachRenderableMediaAssetFields as b, customLinkSchema$1 as bi, boxBackgroundSchema as bn, normalizeCardRole as br, createClientForEnv as bt, sha256 as c, featuresFromHex as ci, getGradientPreset as cn, ACCORDION_OPEN_ACCENT_THICKNESS as cr, createContentEntryRef as ct, walkLocalContentMedia as d, buttonVariantIdSchema as di, backgroundAppearanceColorTokenNames as dn, ACCORDION_TRIGGER_LETTER_SPACING as dr, createFooterRef as dt, surfaceForegroundDeclarations as ei, createManagementClient as en, NAV_LINK_LETTER_SPACING_MAP as er, hasConfiguredBookingVertical as et, buildStorageUrl as f, unsafeAsOpaqueButtonColourToken as fi, backgroundAppearanceQuickColorTokenNames as fn, ICON_CONFIG as fr, createFormRef as ft, buildSettingsUpdateInput as g, mediaTypeDiscriminants as gi, sdkSettingsUpdateSchema as gn, generateCardCss as gr, createVenueRef as gt, SETTINGS_FILE_FIELDS as h, mediaSchema as hi, defineBlockDesignMetadata as hn, getDefaultCardVariants as hr, createSiteCommandSiteRef as ht, syncLocalMediaForPush as i, isOnColorBackgroundCandidateToken as ii, readHeaderNavFontSizePreference as in, FONT_WEIGHT_MAP as ir, generateCssVars as it, getDirectImageUrlForSiteScopedPath as j, __toESM as ji, MQ_SM_DOWN as jn, isCssColorLiteral as jr, parseJsonArray as jt, resolveMediaTransformForAspect as k, pageLinkSchema$1 as ki, MQ_LG_UP as kn, deriveButtonSurfaceContext as kr, handleCommandError as kt, normalizeMediaIdentifierInput as l, hexToRgb$1 as li, parseGradientPresetId as ln, ACCORDION_SPACING_MAP as lr, createEventCategoryRef as lt, extractMediaIdentifiers as m, mediaIdentifierSchema as mi, defaultThemeTokens as mn, generateCardCoreCss as mr, createPageRef as mt, createStyleCommand as n, surfaceForegroundValue as ni, ManagementApiError as nn, NAV_LINK_PADDING_X_MAP as nr, require_react as nt, applyLocalMediaPush as o, getTextColorForBackground as oi, gradientToCss as on, INPUT_SPACING_MAP as or, createBlocksRef as ot, downloadMedia as p, parseBlockColorToken as pi, parseBackgroundAppearanceColorToken as pn, ICON_SIZE_PX as pr, createNavigationRef as pt, getSiteCommandMetadata as q, tileTreatmentValues as qn, parseThemeButtonVariantId as qr, CliExit as qt, revalidateLocalMediaPushPlan as r, expandPalette as ri, mergeHeaderNavTypographyPreference as rn, NAV_LINK_TEXT_SIZE_MAP as rr, pickShadeTowardBackground as rt, prepareLocalMediaForPush as s, ORDINARY_TEXT_MIN_CONTRAST_RATIO as si, GRADIENT_PRESET_IDS as sn, LABEL_FONT_SIZE_MAP as sr, createCommandRef as st, buildStyleSelectionInitializeCommandFromOptions as t, surfaceForegroundStyle as ti, formatIgnoredDocumentSyncSectionWarning as tn, NAV_LINK_PADDING_MAP as tr, require_jsx_runtime as tt, normalizeMediaValueToIdentifierOnly as u, VARIANT_ROLES as ui, boundaryRuntimeMetricsForTransition as un, ACCORDION_TRIGGER_FONT_SCALE as ur, createEventSeriesRef as ut, writeMediaManifest as v, linkPayloadSchema as vi, themeSchema as vn, cardRoleSchema as vr, slugPart as vt, looksLikeUuid as w, internalResolvedLinkSchema as wi, renderHeaderPaintTargetRules as wn, RESOLVED_BUTTON_DISABLED_DECLARATIONS as wr, createOutputForCommand as wt, buildRenderableMediaAssetIndex as x, entryLinkSchema$1 as xi, generateHeaderScrollTreatmentStyles as xn, PUBLIC_BUTTON_ROLES as xr, createCommandContext as xt, isMediaValue as y, RESOLVED_INTERNAL_LINK_ENTITY_TYPES as yi, boxBackgroundOverlaySchema as yn, cardVariantRoleValues as yr, confirmAction as yt, venueManagementFields as z, defineNoBlockDesignCapability as zn, rgbColorValue as zr, withOutputOnlyErrorHandling 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 };