@ohhwells/bridge 0.1.126 → 0.1.127-next.351

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.
package/dist/index.d.cts CHANGED
@@ -20,14 +20,7 @@ declare function OhhwellsBridge(): react_jsx_runtime.JSX.Element;
20
20
  */
21
21
  declare const AI_TREE_SCHEMA_VERSIONS: readonly [1];
22
22
  type SectionScaffold = 'stacked' | 'split' | 'overlay';
23
- type AiBlockType = 'text' | 'button' | 'media' | 'media-list' | 'rating' | 'divider' | 'input' | 'section-header' | 'button-row' | 'card' | 'collection' | 'accordion' | 'media-strip' | 'form' | 'embed' | 'schedule-widget' | 'group';
24
- /** media-strip's `items[]` entry shape (rn-mobile-backend's `itemFields` for that block). */
25
- interface MediaStripItem {
26
- assetId: string;
27
- alt: string;
28
- href?: string;
29
- linkTarget?: 'new-tab' | 'same-tab';
30
- }
23
+ type AiBlockType = 'text' | 'button' | 'media' | 'media-list' | 'rating' | 'divider' | 'input' | 'section-header' | 'button-row' | 'card' | 'collection' | 'accordion' | 'media-strip' | 'form' | 'embed' | 'schedule-widget' | 'group' | 'link-list' | 'hours' | 'logo' | 'social-row' | 'video';
31
24
  interface AiBlockNode {
32
25
  type: AiBlockType;
33
26
  span: number;
@@ -36,11 +29,8 @@ interface AiBlockNode {
36
29
  slots?: Record<string, unknown>;
37
30
  children?: AiBlockNode[];
38
31
  /** Shape is per-block-type, defined by the backend's `BlockDef.itemFields` (accordion's
39
- * question/answer, media-strip's assetId/alt/href/linkTarget). */
40
- items?: Array<{
41
- question: string;
42
- answer: string;
43
- } | MediaStripItem>;
32
+ * question/answer, media-strip's assetId/alt/href/linkTarget, footer link-list/hours/social-row). */
33
+ items?: Array<Record<string, unknown>>;
44
34
  animation?: {
45
35
  effect: 'fade-in' | 'fade-in-up' | 'slide-in-left' | 'slide-in-right' | 'zoom-in';
46
36
  trigger: 'on-scroll' | 'on-load' | 'on-hover';
@@ -49,7 +39,15 @@ interface AiBlockNode {
49
39
  };
50
40
  }
51
41
  interface AiSectionSettings {
42
+ /** @deprecated OHH-789 folds this into verticalPadding — kept readable so already-persisted
43
+ * trees carrying tight/balanced/airy still render; see legacyVerticalPaddingAlias below. */
52
44
  spacing?: 'tight' | 'balanced' | 'airy';
45
+ /** Section-node vertical padding (OHH-789), any scaffold. Default 'm'. 'none' is a true 0px
46
+ * floor, below 'xs', for an explicit "remove the padding" ask. */
47
+ verticalPadding?: 'none' | 'xs' | 's' | 'm' | 'l' | 'xl';
48
+ /** Section-node minimum-height floor (OHH-789), any scaffold. Default 'auto' — a floor, never
49
+ * a fixed height, so content taller than 'half'/'full' always extends the section. */
50
+ minHeight?: 'auto' | 'half' | 'full';
53
51
  alignment?: 'left' | 'center';
54
52
  ratio?: '1:1' | '5:3' | '3:5';
55
53
  verticalPosition?: 'top' | 'center';
@@ -168,10 +166,16 @@ declare const AI_TREE_TOKENS: {
168
166
  readonly radiusButton: 10;
169
167
  readonly radiusCard: 18;
170
168
  readonly sectionMaxWidth: 1280;
171
- readonly sectionPadding: {
172
- readonly tight: 40;
173
- readonly balanced: 64;
174
- readonly airy: 96;
169
+ /** 6-step vertical-padding scale (OHH-789) — replaces the deprecated tight/balanced/airy
170
+ * map; `m` keeps the old `balanced` value (64) so already-generated sites render unchanged.
171
+ * `none` is the true zero floor for an explicit "remove the padding" ask. */
172
+ readonly sectionPaddingByScale: {
173
+ readonly none: 0;
174
+ readonly xs: 24;
175
+ readonly s: 40;
176
+ readonly m: 64;
177
+ readonly l: 96;
178
+ readonly xl: 128;
175
179
  };
176
180
  readonly type: {
177
181
  readonly display: {
@@ -218,6 +222,37 @@ declare const AI_TREE_TOKENS: {
218
222
  };
219
223
  declare function isRenderableTree(value: unknown): value is AiLayoutTree;
220
224
 
225
+ /**
226
+ * Footer layout-tree contract — bridge-side mirror of
227
+ * `rn-mobile-backend/app/helpers/ai-footer-tree.ts`.
228
+ *
229
+ * The server validates every footer tree fail-closed before it reaches the bridge.
230
+ * Legal links are never on this tree; the renderer injects them from site legal config.
231
+ */
232
+
233
+ declare const FOOTER_COLUMN_SPANS: readonly [12, 6, 4, 3];
234
+ type FooterColumnSpan = (typeof FOOTER_COLUMN_SPANS)[number];
235
+ declare const FOOTER_COLUMN_BLOCKS: readonly ["link-list", "text", "hours", "logo", "social-row", "media-strip"];
236
+ declare const FOOTER_SOCIAL_PLATFORMS: readonly ["facebook", "instagram", "youtube", "linkedin", "twitter", "tiktok", "threads", "pinterest", "whatsapp"];
237
+ interface AiFooterColumn {
238
+ span: FooterColumnSpan;
239
+ blocks: AiBlockNode[];
240
+ }
241
+ /** Protected bottom bar — copyright only; legal links come from site config at render. */
242
+ interface AiFooterBottomBar {
243
+ /** May include `{year}` — resolved at render time. */
244
+ copyright: string;
245
+ }
246
+ interface AiFooterLayoutTree {
247
+ schemaVersion: number;
248
+ type: 'footer';
249
+ columns: AiFooterColumn[];
250
+ bottomBar: AiFooterBottomBar;
251
+ }
252
+ /** Resolve `{year}` in copyright copy — never ship a stale year. */
253
+ declare function resolveFooterCopyright(copyright: string, year?: number): string;
254
+ declare function isRenderableFooterTree(value: unknown): value is AiFooterLayoutTree;
255
+
221
256
  interface AiTreeRendererProps {
222
257
  tree: AiLayoutTree;
223
258
  brand?: AiBrandKit;
@@ -233,6 +268,118 @@ interface AiTreeRendererProps {
233
268
  editKeyPrefix?: string;
234
269
  }
235
270
  declare function AiTreeRenderer({ tree, brand, buttonStyle, resolveMedia, editKeyPrefix, }: AiTreeRendererProps): react_jsx_runtime.JSX.Element | null;
271
+ /** Tier 1 legal links — always rendered from site config, never from the footer tree. */
272
+ interface SiteLegalLinks {
273
+ privacy: {
274
+ label: string;
275
+ href: string;
276
+ };
277
+ terms: {
278
+ label: string;
279
+ href: string;
280
+ };
281
+ /** Cookie-settings control; omit to hide. Trigger only — no editable href. */
282
+ cookieSettings?: {
283
+ label: string;
284
+ onClick?: () => void;
285
+ };
286
+ }
287
+ interface AiFooterRendererProps {
288
+ tree: AiFooterLayoutTree;
289
+ brand?: AiBrandKit;
290
+ resolveMedia?: (ref: string) => string | null;
291
+ editKeyPrefix?: string;
292
+ /** Brand logo URL for `logo` blocks. */
293
+ logoUrl?: string | null;
294
+ /** Map page/anchor ids → navigable hrefs for link-list internals. */
295
+ resolvePageHref?: (pageOrAnchorId: string) => string | null;
296
+ /** Always painted in the bottom bar (Tier 1). */
297
+ legal: SiteLegalLinks;
298
+ /** Template footer's own surface — retrofit paints on the real band, not the brand's dark. */
299
+ surface?: {
300
+ background: string;
301
+ text: string;
302
+ } | null;
303
+ }
304
+ /**
305
+ * Site-wide footer from a validated footer layout tree .
306
+ * Bottom bar always includes copyright (`{year}` resolved) + legal links from `legal`.
307
+ */
308
+ declare function AiFooterRenderer({ tree, brand, resolveMedia, editKeyPrefix, logoUrl, resolvePageHref, legal, surface, }: AiFooterRendererProps): react_jsx_runtime.JSX.Element | null;
309
+
310
+ /**
311
+ * Site-wide AI footer persistence .
312
+ *
313
+ * One tree per site under `__ohw_ai_footer` — same Tier 1 guarded namespace as
314
+ * `__ohw_ai_sections`. Absent / empty = keep the template React footer
315
+ * (pixel-identical retrofit). Present = mount AiFooterRenderer and hide the
316
+ * template footer chrome.
317
+ */
318
+
319
+ declare const AI_FOOTER_KEY = "__ohw_ai_footer";
320
+ interface AiFooterState {
321
+ v: 1;
322
+ tree: AiFooterLayoutTree;
323
+ media?: Record<string, string>;
324
+ brand?: AiBrandKit;
325
+ }
326
+ /** Parse a persisted footer value — malformed state means "no custom footer". */
327
+ declare function parseAiFooterState(raw: string | null | undefined): AiFooterState | null;
328
+ declare function serializeAiFooterState(state: AiFooterState): string;
329
+
330
+ /**
331
+ * Default footer layout tree — bridge mirror of
332
+ * `rn-mobile-backend/app/helpers/ai-footer-default.ts`.
333
+ *
334
+ * Used when an AI edit first materialises a footer tree from the house default,
335
+ * and as the seed for prompt structured edits. Template footers stay until then.
336
+ */
337
+
338
+ declare function buildDefaultFooterTree(opts?: {
339
+ businessName?: string;
340
+ about?: string;
341
+ pageLinks?: Array<{
342
+ label: string;
343
+ href: string;
344
+ }>;
345
+ }): AiFooterLayoutTree;
346
+
347
+ /**
348
+ * Pure structured edits for the site-wide footer tree .
349
+ * Prompt intents map here; the bridge/persist layer validates via the backend
350
+ * (or client-side shape checks) before applying.
351
+ */
352
+
353
+ /** Ensure a footer tree exists — seed from the house default when first editing. */
354
+ declare function ensureFooterTree(tree: AiFooterLayoutTree | null | undefined, opts?: {
355
+ businessName?: string;
356
+ }): AiFooterLayoutTree;
357
+ /**
358
+ * Add or update a social platform link. Same platform → update href (no duplicate icons);
359
+ * missing row → create `social-row` in the emptiest column.
360
+ */
361
+ declare function appendFooterSocial(tree: AiFooterLayoutTree, entry: {
362
+ platform: string;
363
+ href: string;
364
+ }): AiFooterLayoutTree;
365
+ /** Insert an hours block into the emptiest/last column. */
366
+ declare function insertFooterHours(tree: AiFooterLayoutTree, hours: Array<{
367
+ day: string;
368
+ hours: string;
369
+ }>, title?: string): AiFooterLayoutTree;
370
+ /**
371
+ * Add a link-list column (≤4 link columns). Packs brand first so it does not
372
+ * burn a slot. Rejects when already at 4 link-lists (returns original).
373
+ */
374
+ declare function addFooterLinkColumn(tree: AiFooterLayoutTree, column: {
375
+ title?: string;
376
+ links: Array<{
377
+ label: string;
378
+ href: string;
379
+ }>;
380
+ }): AiFooterLayoutTree;
381
+ /** Remove the first block matching `predicate` (e.g. about text). */
382
+ declare function removeFooterBlock(tree: AiFooterLayoutTree, predicate: (block: AiBlockNode, columnIndex: number, blockIndex: number) => boolean): AiFooterLayoutTree;
236
383
 
237
384
  type LinkPage = {
238
385
  path: string;
@@ -545,4 +692,4 @@ declare function useOhwCarousel(key: string, initial: CarouselSlide[]): {
545
692
  bind: Record<string, string>;
546
693
  };
547
694
 
548
- export { AI_DEFAULT_BRAND, AI_TREE_SCHEMA_VERSIONS, AI_TREE_TOKENS, type AiBlockNode, type AiBrandKit, type AiLayoutTree, AiTreeRenderer, type AiTreeRendererProps, type CarouselSlide, CustomToolbar, CustomToolbarButton, CustomToolbarDivider, DragHandle, DropIndicator, type DropIndicatorProps, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptySection, type EmptySectionProps, ItemActionToolbar, type ItemActionToolbarProps, ItemInteractionLayer, type ItemInteractionLayerProps, type ItemInteractionState, LinkEditorPanel, type LinkModalMode, type LinkModalProps, type LinkPage, LinkPopover, type LinkSection, OhhwellsBridge, SchedulingWidget, type SectionScaffold, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, buildTarget, dropIndicatorVariants, filterAvailablePages, getEditModeInitialState, isEditSessionActive, isRenderableTree, isValidUrl, parseTarget, toggleVariants, useOhwCarousel, validateUrlInput };
695
+ export { AI_DEFAULT_BRAND, AI_FOOTER_KEY, AI_TREE_SCHEMA_VERSIONS, AI_TREE_TOKENS, type AiBlockNode, type AiBrandKit, type AiFooterBottomBar, type AiFooterColumn, type AiFooterLayoutTree, AiFooterRenderer, type AiFooterRendererProps, type AiLayoutTree, AiTreeRenderer, type AiTreeRendererProps, type CarouselSlide, CustomToolbar, CustomToolbarButton, CustomToolbarDivider, DragHandle, DropIndicator, type DropIndicatorProps, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptySection, type EmptySectionProps, FOOTER_COLUMN_BLOCKS, FOOTER_COLUMN_SPANS, FOOTER_SOCIAL_PLATFORMS, ItemActionToolbar, type ItemActionToolbarProps, ItemInteractionLayer, type ItemInteractionLayerProps, type ItemInteractionState, LinkEditorPanel, type LinkModalMode, type LinkModalProps, type LinkPage, LinkPopover, type LinkSection, OhhwellsBridge, SchedulingWidget, type SectionScaffold, type SiteLegalLinks, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, addFooterLinkColumn, appendFooterSocial, buildDefaultFooterTree, buildTarget, dropIndicatorVariants, ensureFooterTree, filterAvailablePages, getEditModeInitialState, insertFooterHours, isEditSessionActive, isRenderableFooterTree, isRenderableTree, isValidUrl, parseAiFooterState, parseTarget, removeFooterBlock, resolveFooterCopyright, serializeAiFooterState, toggleVariants, useOhwCarousel, validateUrlInput };
package/dist/index.d.ts CHANGED
@@ -20,14 +20,7 @@ declare function OhhwellsBridge(): react_jsx_runtime.JSX.Element;
20
20
  */
21
21
  declare const AI_TREE_SCHEMA_VERSIONS: readonly [1];
22
22
  type SectionScaffold = 'stacked' | 'split' | 'overlay';
23
- type AiBlockType = 'text' | 'button' | 'media' | 'media-list' | 'rating' | 'divider' | 'input' | 'section-header' | 'button-row' | 'card' | 'collection' | 'accordion' | 'media-strip' | 'form' | 'embed' | 'schedule-widget' | 'group';
24
- /** media-strip's `items[]` entry shape (rn-mobile-backend's `itemFields` for that block). */
25
- interface MediaStripItem {
26
- assetId: string;
27
- alt: string;
28
- href?: string;
29
- linkTarget?: 'new-tab' | 'same-tab';
30
- }
23
+ type AiBlockType = 'text' | 'button' | 'media' | 'media-list' | 'rating' | 'divider' | 'input' | 'section-header' | 'button-row' | 'card' | 'collection' | 'accordion' | 'media-strip' | 'form' | 'embed' | 'schedule-widget' | 'group' | 'link-list' | 'hours' | 'logo' | 'social-row' | 'video';
31
24
  interface AiBlockNode {
32
25
  type: AiBlockType;
33
26
  span: number;
@@ -36,11 +29,8 @@ interface AiBlockNode {
36
29
  slots?: Record<string, unknown>;
37
30
  children?: AiBlockNode[];
38
31
  /** Shape is per-block-type, defined by the backend's `BlockDef.itemFields` (accordion's
39
- * question/answer, media-strip's assetId/alt/href/linkTarget). */
40
- items?: Array<{
41
- question: string;
42
- answer: string;
43
- } | MediaStripItem>;
32
+ * question/answer, media-strip's assetId/alt/href/linkTarget, footer link-list/hours/social-row). */
33
+ items?: Array<Record<string, unknown>>;
44
34
  animation?: {
45
35
  effect: 'fade-in' | 'fade-in-up' | 'slide-in-left' | 'slide-in-right' | 'zoom-in';
46
36
  trigger: 'on-scroll' | 'on-load' | 'on-hover';
@@ -49,7 +39,15 @@ interface AiBlockNode {
49
39
  };
50
40
  }
51
41
  interface AiSectionSettings {
42
+ /** @deprecated OHH-789 folds this into verticalPadding — kept readable so already-persisted
43
+ * trees carrying tight/balanced/airy still render; see legacyVerticalPaddingAlias below. */
52
44
  spacing?: 'tight' | 'balanced' | 'airy';
45
+ /** Section-node vertical padding (OHH-789), any scaffold. Default 'm'. 'none' is a true 0px
46
+ * floor, below 'xs', for an explicit "remove the padding" ask. */
47
+ verticalPadding?: 'none' | 'xs' | 's' | 'm' | 'l' | 'xl';
48
+ /** Section-node minimum-height floor (OHH-789), any scaffold. Default 'auto' — a floor, never
49
+ * a fixed height, so content taller than 'half'/'full' always extends the section. */
50
+ minHeight?: 'auto' | 'half' | 'full';
53
51
  alignment?: 'left' | 'center';
54
52
  ratio?: '1:1' | '5:3' | '3:5';
55
53
  verticalPosition?: 'top' | 'center';
@@ -168,10 +166,16 @@ declare const AI_TREE_TOKENS: {
168
166
  readonly radiusButton: 10;
169
167
  readonly radiusCard: 18;
170
168
  readonly sectionMaxWidth: 1280;
171
- readonly sectionPadding: {
172
- readonly tight: 40;
173
- readonly balanced: 64;
174
- readonly airy: 96;
169
+ /** 6-step vertical-padding scale (OHH-789) — replaces the deprecated tight/balanced/airy
170
+ * map; `m` keeps the old `balanced` value (64) so already-generated sites render unchanged.
171
+ * `none` is the true zero floor for an explicit "remove the padding" ask. */
172
+ readonly sectionPaddingByScale: {
173
+ readonly none: 0;
174
+ readonly xs: 24;
175
+ readonly s: 40;
176
+ readonly m: 64;
177
+ readonly l: 96;
178
+ readonly xl: 128;
175
179
  };
176
180
  readonly type: {
177
181
  readonly display: {
@@ -218,6 +222,37 @@ declare const AI_TREE_TOKENS: {
218
222
  };
219
223
  declare function isRenderableTree(value: unknown): value is AiLayoutTree;
220
224
 
225
+ /**
226
+ * Footer layout-tree contract — bridge-side mirror of
227
+ * `rn-mobile-backend/app/helpers/ai-footer-tree.ts`.
228
+ *
229
+ * The server validates every footer tree fail-closed before it reaches the bridge.
230
+ * Legal links are never on this tree; the renderer injects them from site legal config.
231
+ */
232
+
233
+ declare const FOOTER_COLUMN_SPANS: readonly [12, 6, 4, 3];
234
+ type FooterColumnSpan = (typeof FOOTER_COLUMN_SPANS)[number];
235
+ declare const FOOTER_COLUMN_BLOCKS: readonly ["link-list", "text", "hours", "logo", "social-row", "media-strip"];
236
+ declare const FOOTER_SOCIAL_PLATFORMS: readonly ["facebook", "instagram", "youtube", "linkedin", "twitter", "tiktok", "threads", "pinterest", "whatsapp"];
237
+ interface AiFooterColumn {
238
+ span: FooterColumnSpan;
239
+ blocks: AiBlockNode[];
240
+ }
241
+ /** Protected bottom bar — copyright only; legal links come from site config at render. */
242
+ interface AiFooterBottomBar {
243
+ /** May include `{year}` — resolved at render time. */
244
+ copyright: string;
245
+ }
246
+ interface AiFooterLayoutTree {
247
+ schemaVersion: number;
248
+ type: 'footer';
249
+ columns: AiFooterColumn[];
250
+ bottomBar: AiFooterBottomBar;
251
+ }
252
+ /** Resolve `{year}` in copyright copy — never ship a stale year. */
253
+ declare function resolveFooterCopyright(copyright: string, year?: number): string;
254
+ declare function isRenderableFooterTree(value: unknown): value is AiFooterLayoutTree;
255
+
221
256
  interface AiTreeRendererProps {
222
257
  tree: AiLayoutTree;
223
258
  brand?: AiBrandKit;
@@ -233,6 +268,118 @@ interface AiTreeRendererProps {
233
268
  editKeyPrefix?: string;
234
269
  }
235
270
  declare function AiTreeRenderer({ tree, brand, buttonStyle, resolveMedia, editKeyPrefix, }: AiTreeRendererProps): react_jsx_runtime.JSX.Element | null;
271
+ /** Tier 1 legal links — always rendered from site config, never from the footer tree. */
272
+ interface SiteLegalLinks {
273
+ privacy: {
274
+ label: string;
275
+ href: string;
276
+ };
277
+ terms: {
278
+ label: string;
279
+ href: string;
280
+ };
281
+ /** Cookie-settings control; omit to hide. Trigger only — no editable href. */
282
+ cookieSettings?: {
283
+ label: string;
284
+ onClick?: () => void;
285
+ };
286
+ }
287
+ interface AiFooterRendererProps {
288
+ tree: AiFooterLayoutTree;
289
+ brand?: AiBrandKit;
290
+ resolveMedia?: (ref: string) => string | null;
291
+ editKeyPrefix?: string;
292
+ /** Brand logo URL for `logo` blocks. */
293
+ logoUrl?: string | null;
294
+ /** Map page/anchor ids → navigable hrefs for link-list internals. */
295
+ resolvePageHref?: (pageOrAnchorId: string) => string | null;
296
+ /** Always painted in the bottom bar (Tier 1). */
297
+ legal: SiteLegalLinks;
298
+ /** Template footer's own surface — retrofit paints on the real band, not the brand's dark. */
299
+ surface?: {
300
+ background: string;
301
+ text: string;
302
+ } | null;
303
+ }
304
+ /**
305
+ * Site-wide footer from a validated footer layout tree .
306
+ * Bottom bar always includes copyright (`{year}` resolved) + legal links from `legal`.
307
+ */
308
+ declare function AiFooterRenderer({ tree, brand, resolveMedia, editKeyPrefix, logoUrl, resolvePageHref, legal, surface, }: AiFooterRendererProps): react_jsx_runtime.JSX.Element | null;
309
+
310
+ /**
311
+ * Site-wide AI footer persistence .
312
+ *
313
+ * One tree per site under `__ohw_ai_footer` — same Tier 1 guarded namespace as
314
+ * `__ohw_ai_sections`. Absent / empty = keep the template React footer
315
+ * (pixel-identical retrofit). Present = mount AiFooterRenderer and hide the
316
+ * template footer chrome.
317
+ */
318
+
319
+ declare const AI_FOOTER_KEY = "__ohw_ai_footer";
320
+ interface AiFooterState {
321
+ v: 1;
322
+ tree: AiFooterLayoutTree;
323
+ media?: Record<string, string>;
324
+ brand?: AiBrandKit;
325
+ }
326
+ /** Parse a persisted footer value — malformed state means "no custom footer". */
327
+ declare function parseAiFooterState(raw: string | null | undefined): AiFooterState | null;
328
+ declare function serializeAiFooterState(state: AiFooterState): string;
329
+
330
+ /**
331
+ * Default footer layout tree — bridge mirror of
332
+ * `rn-mobile-backend/app/helpers/ai-footer-default.ts`.
333
+ *
334
+ * Used when an AI edit first materialises a footer tree from the house default,
335
+ * and as the seed for prompt structured edits. Template footers stay until then.
336
+ */
337
+
338
+ declare function buildDefaultFooterTree(opts?: {
339
+ businessName?: string;
340
+ about?: string;
341
+ pageLinks?: Array<{
342
+ label: string;
343
+ href: string;
344
+ }>;
345
+ }): AiFooterLayoutTree;
346
+
347
+ /**
348
+ * Pure structured edits for the site-wide footer tree .
349
+ * Prompt intents map here; the bridge/persist layer validates via the backend
350
+ * (or client-side shape checks) before applying.
351
+ */
352
+
353
+ /** Ensure a footer tree exists — seed from the house default when first editing. */
354
+ declare function ensureFooterTree(tree: AiFooterLayoutTree | null | undefined, opts?: {
355
+ businessName?: string;
356
+ }): AiFooterLayoutTree;
357
+ /**
358
+ * Add or update a social platform link. Same platform → update href (no duplicate icons);
359
+ * missing row → create `social-row` in the emptiest column.
360
+ */
361
+ declare function appendFooterSocial(tree: AiFooterLayoutTree, entry: {
362
+ platform: string;
363
+ href: string;
364
+ }): AiFooterLayoutTree;
365
+ /** Insert an hours block into the emptiest/last column. */
366
+ declare function insertFooterHours(tree: AiFooterLayoutTree, hours: Array<{
367
+ day: string;
368
+ hours: string;
369
+ }>, title?: string): AiFooterLayoutTree;
370
+ /**
371
+ * Add a link-list column (≤4 link columns). Packs brand first so it does not
372
+ * burn a slot. Rejects when already at 4 link-lists (returns original).
373
+ */
374
+ declare function addFooterLinkColumn(tree: AiFooterLayoutTree, column: {
375
+ title?: string;
376
+ links: Array<{
377
+ label: string;
378
+ href: string;
379
+ }>;
380
+ }): AiFooterLayoutTree;
381
+ /** Remove the first block matching `predicate` (e.g. about text). */
382
+ declare function removeFooterBlock(tree: AiFooterLayoutTree, predicate: (block: AiBlockNode, columnIndex: number, blockIndex: number) => boolean): AiFooterLayoutTree;
236
383
 
237
384
  type LinkPage = {
238
385
  path: string;
@@ -545,4 +692,4 @@ declare function useOhwCarousel(key: string, initial: CarouselSlide[]): {
545
692
  bind: Record<string, string>;
546
693
  };
547
694
 
548
- export { AI_DEFAULT_BRAND, AI_TREE_SCHEMA_VERSIONS, AI_TREE_TOKENS, type AiBlockNode, type AiBrandKit, type AiLayoutTree, AiTreeRenderer, type AiTreeRendererProps, type CarouselSlide, CustomToolbar, CustomToolbarButton, CustomToolbarDivider, DragHandle, DropIndicator, type DropIndicatorProps, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptySection, type EmptySectionProps, ItemActionToolbar, type ItemActionToolbarProps, ItemInteractionLayer, type ItemInteractionLayerProps, type ItemInteractionState, LinkEditorPanel, type LinkModalMode, type LinkModalProps, type LinkPage, LinkPopover, type LinkSection, OhhwellsBridge, SchedulingWidget, type SectionScaffold, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, buildTarget, dropIndicatorVariants, filterAvailablePages, getEditModeInitialState, isEditSessionActive, isRenderableTree, isValidUrl, parseTarget, toggleVariants, useOhwCarousel, validateUrlInput };
695
+ export { AI_DEFAULT_BRAND, AI_FOOTER_KEY, AI_TREE_SCHEMA_VERSIONS, AI_TREE_TOKENS, type AiBlockNode, type AiBrandKit, type AiFooterBottomBar, type AiFooterColumn, type AiFooterLayoutTree, AiFooterRenderer, type AiFooterRendererProps, type AiLayoutTree, AiTreeRenderer, type AiTreeRendererProps, type CarouselSlide, CustomToolbar, CustomToolbarButton, CustomToolbarDivider, DragHandle, DropIndicator, type DropIndicatorProps, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptySection, type EmptySectionProps, FOOTER_COLUMN_BLOCKS, FOOTER_COLUMN_SPANS, FOOTER_SOCIAL_PLATFORMS, ItemActionToolbar, type ItemActionToolbarProps, ItemInteractionLayer, type ItemInteractionLayerProps, type ItemInteractionState, LinkEditorPanel, type LinkModalMode, type LinkModalProps, type LinkPage, LinkPopover, type LinkSection, OhhwellsBridge, SchedulingWidget, type SectionScaffold, type SiteLegalLinks, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, addFooterLinkColumn, appendFooterSocial, buildDefaultFooterTree, buildTarget, dropIndicatorVariants, ensureFooterTree, filterAvailablePages, getEditModeInitialState, insertFooterHours, isEditSessionActive, isRenderableFooterTree, isRenderableTree, isValidUrl, parseAiFooterState, parseTarget, removeFooterBlock, resolveFooterCopyright, serializeAiFooterState, toggleVariants, useOhwCarousel, validateUrlInput };