@pushwoosh/rpc-v2-http-api-data 0.2.122 → 0.2.124
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/accounts.d.ts +20 -3
- package/data.js +1884 -0
- package/package.json +1 -1
- package/pushwoosh_aibuilder_v1.d.ts +1491 -0
- package/pushwoosh_aibuilder_v1.js +3 -0
- package/pushwoosh_llm_v1.d.ts +68 -0
- package/pushwoosh_llm_v1.js +3 -0
|
@@ -0,0 +1,1491 @@
|
|
|
1
|
+
export type DocumentBlockType = 'DOCUMENT_BLOCK_TYPE_UNSPECIFIED' | 'DOCUMENT_BLOCK_TYPE_CARD' | 'DOCUMENT_BLOCK_TYPE_COLUMNS'
|
|
2
|
+
/**
|
|
3
|
+
* Top-level free-form Content block (text + buttons), no card
|
|
4
|
+
* framing or image slot. Allowed in WEB_POPUP only.
|
|
5
|
+
*/
|
|
6
|
+
| 'DOCUMENT_BLOCK_TYPE_CONTENT'
|
|
7
|
+
/**
|
|
8
|
+
* Structural stack container — a "group" block in the kind registry.
|
|
9
|
+
* Wire-shape is AiBuilderBlock.group. Allowed in EMAIL and WEB_POPUP.
|
|
10
|
+
*/
|
|
11
|
+
| 'DOCUMENT_BLOCK_TYPE_GROUP'
|
|
12
|
+
/**
|
|
13
|
+
* Client-created empty slot meant to be materialised into a real
|
|
14
|
+
* block by the AI on a follow-up edit. Wire-shape is
|
|
15
|
+
* AiBuilderBlock.placeholder.
|
|
16
|
+
*/
|
|
17
|
+
| 'DOCUMENT_BLOCK_TYPE_PLACEHOLDER';
|
|
18
|
+
/**
|
|
19
|
+
* GroupDirection picks which axis an AiBuilderGroup lays its children
|
|
20
|
+
* along.
|
|
21
|
+
*
|
|
22
|
+
* Wire contract:
|
|
23
|
+
* UNSPECIFIED — "inherit / use the document default" (currently
|
|
24
|
+
* rendered as a vertical stack). Render-time fallback
|
|
25
|
+
* only; NOT wire-equivalent to VERTICAL — downstream
|
|
26
|
+
* consumers that diff documents see them as different
|
|
27
|
+
* values.
|
|
28
|
+
* VERTICAL — explicit vertical stack. Reserved for an "Explicit
|
|
29
|
+
* override" UX where the client wants to pin the value
|
|
30
|
+
* against a future default change. The backend never
|
|
31
|
+
* emits this on freshly generated groups; that case
|
|
32
|
+
* stays UNSPECIFIED.
|
|
33
|
+
* HORIZONTAL — explicit side-by-side layout. Emitted only by the
|
|
34
|
+
* client (the LLM never picks a direction).
|
|
35
|
+
*
|
|
36
|
+
* Recommended client flow: send UNSPECIFIED for the default and switch
|
|
37
|
+
* to a concrete value only when the user explicitly chose a non-default
|
|
38
|
+
* direction; revert to UNSPECIFIED when the user resets it.
|
|
39
|
+
*
|
|
40
|
+
* Server contract: the backend preserves whatever value the client
|
|
41
|
+
* sent across edits — direction is in the client-managed set on
|
|
42
|
+
* AiBuilderGroup. The enum lives at file scope so the same shape is
|
|
43
|
+
* shared between the document model and the AiGen wire types.
|
|
44
|
+
*/
|
|
45
|
+
export type GroupDirection = 'GROUP_DIRECTION_UNSPECIFIED' | 'GROUP_DIRECTION_VERTICAL' | 'GROUP_DIRECTION_HORIZONTAL';
|
|
46
|
+
export type CardLayout = 'CARD_LAYOUT_UNSPECIFIED' | 'CARD_LAYOUT_RIGHT' | 'CARD_LAYOUT_LEFT' | 'CARD_LAYOUT_TOP' | 'CARD_LAYOUT_NO'
|
|
47
|
+
/**
|
|
48
|
+
* The card image renders as the card backdrop (dark overlay + light text).
|
|
49
|
+
* Takes precedence over BlockStyles.background_image on the same block.
|
|
50
|
+
*/
|
|
51
|
+
| 'CARD_LAYOUT_BACKGROUND';
|
|
52
|
+
export type ButtonGroupAlign = 'BUTTON_GROUP_ALIGN_UNSPECIFIED' | 'BUTTON_GROUP_ALIGN_LEFT' | 'BUTTON_GROUP_ALIGN_CENTER' | 'BUTTON_GROUP_ALIGN_RIGHT';
|
|
53
|
+
/**
|
|
54
|
+
* TextAlign is the horizontal alignment of a single TextBlock's copy.
|
|
55
|
+
* UNSPECIFIED = inherit (renders left). LLM-managed: the content generator
|
|
56
|
+
* sets it for headline/caption centring; preserved verbatim on edit.
|
|
57
|
+
*/
|
|
58
|
+
export type TextAlign = 'TEXT_ALIGN_UNSPECIFIED' | 'TEXT_ALIGN_LEFT' | 'TEXT_ALIGN_CENTER' | 'TEXT_ALIGN_RIGHT';
|
|
59
|
+
/**
|
|
60
|
+
* GroupAlign controls alignment of an AiBuilderGroup's children along
|
|
61
|
+
* the perpendicular axis (for direction=VERTICAL → horizontal
|
|
62
|
+
* alignment of children; for direction=HORIZONTAL → vertical
|
|
63
|
+
* alignment). Named in start/center/end terms (not left/right)
|
|
64
|
+
* because the concrete axis depends on direction.
|
|
65
|
+
*
|
|
66
|
+
* Wire contract:
|
|
67
|
+
* UNSPECIFIED — "inherit / use the document default" (currently
|
|
68
|
+
* rendered as START). Render-time fallback only; NOT
|
|
69
|
+
* wire-equivalent to START.
|
|
70
|
+
* START — explicit start alignment. Reserved for the same
|
|
71
|
+
* "Explicit override" UX as GroupDirection.VERTICAL.
|
|
72
|
+
* The backend never emits this on freshly generated
|
|
73
|
+
* groups; that case stays UNSPECIFIED.
|
|
74
|
+
* CENTER/END — explicit center / end alignment.
|
|
75
|
+
*
|
|
76
|
+
* Recommended client flow: same as GroupDirection — UNSPECIFIED for the
|
|
77
|
+
* default, a concrete value only when the user explicitly chose one,
|
|
78
|
+
* back to UNSPECIFIED on reset.
|
|
79
|
+
*/
|
|
80
|
+
export type GroupAlign = 'GROUP_ALIGN_UNSPECIFIED' | 'GROUP_ALIGN_START' | 'GROUP_ALIGN_CENTER' | 'GROUP_ALIGN_END';
|
|
81
|
+
/**
|
|
82
|
+
* CardImageFit controls how the card's image fills its image slot.
|
|
83
|
+
* Why it exists: card slots have fixed proportions while images arrive
|
|
84
|
+
* with arbitrary aspect ratios. Without this knob the client would have
|
|
85
|
+
* to pick a single global default and some cards would always look bad.
|
|
86
|
+
* A product photo is fine with COVER, a company logo must be CONTAIN
|
|
87
|
+
* or it gets cropped.
|
|
88
|
+
*/
|
|
89
|
+
export type CardImageFit = 'CARD_IMAGE_FIT_UNSPECIFIED'
|
|
90
|
+
/** Fill the slot, cropping overflow. Good default for photos. */
|
|
91
|
+
| 'CARD_IMAGE_FIT_COVER'
|
|
92
|
+
/**
|
|
93
|
+
* Fit entirely inside the slot, may leave padding. Required for logos
|
|
94
|
+
* and other assets that must not be cropped.
|
|
95
|
+
*/
|
|
96
|
+
| 'CARD_IMAGE_FIT_CONTAIN'
|
|
97
|
+
/**
|
|
98
|
+
* Stretch to fit, distorting aspect ratio. Deprecated — no longer
|
|
99
|
+
* offered in the editor and dropped from the LLM schema; kept only for
|
|
100
|
+
* wire compatibility.
|
|
101
|
+
*/
|
|
102
|
+
| 'CARD_IMAGE_FIT_FILL';
|
|
103
|
+
/** Unit for CardImageSize.value. */
|
|
104
|
+
export type CardImageSizeUnit =
|
|
105
|
+
/** value ignored → client default */
|
|
106
|
+
'CARD_IMAGE_SIZE_UNIT_UNSPECIFIED'
|
|
107
|
+
/** absolute pixels */
|
|
108
|
+
| 'CARD_IMAGE_SIZE_UNIT_PX'
|
|
109
|
+
/** % of the card content-box width */
|
|
110
|
+
| 'CARD_IMAGE_SIZE_UNIT_PERCENT';
|
|
111
|
+
/**
|
|
112
|
+
* CardImageSize sizes the image slot. Which dimension it drives depends on
|
|
113
|
+
* AiBuilderCard.layout:
|
|
114
|
+
* LEFT / RIGHT → width of the image column
|
|
115
|
+
* TOP → height of the image
|
|
116
|
+
* BACKGROUND → height of the card (width is always 100%)
|
|
117
|
+
* NO → no image, ignored
|
|
118
|
+
* PERCENT is always measured against the card content-box WIDTH, so a
|
|
119
|
+
* percentage height yields a stable aspect ratio regardless of card width.
|
|
120
|
+
* Client-managed: the LLM never sets this; the backend round-trips it
|
|
121
|
+
* verbatim. value <= 0 (or unit UNSPECIFIED) is treated as unset → the
|
|
122
|
+
* client falls back to its default.
|
|
123
|
+
*/
|
|
124
|
+
export type CardImageSize = {
|
|
125
|
+
value: number;
|
|
126
|
+
unit: CardImageSizeUnit;
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* CardImagePosition is the focus / anchor of the image within its slot
|
|
130
|
+
* (CSS object-position). One field serves both UI modes:
|
|
131
|
+
* CONTAIN → "set position" (where the fitted image sits)
|
|
132
|
+
* COVER → "focal point" (which point stays in frame when cropped)
|
|
133
|
+
* Percentages 0..100; (0,0) = top-left, (50,50) = centre. Client-managed:
|
|
134
|
+
* the LLM never sets this; the backend round-trips it verbatim.
|
|
135
|
+
*/
|
|
136
|
+
export type CardImagePosition = {
|
|
137
|
+
/** horizontal, % (left) */
|
|
138
|
+
x: number;
|
|
139
|
+
/** vertical, % (top) */
|
|
140
|
+
y: number;
|
|
141
|
+
};
|
|
142
|
+
export type TextVariantStyle = {
|
|
143
|
+
fontSize: number;
|
|
144
|
+
fontWeight: number;
|
|
145
|
+
lineHeight: number;
|
|
146
|
+
/**
|
|
147
|
+
* Font-family override for this text variant (e.g. "headings in Inter,
|
|
148
|
+
* body in Roboto").
|
|
149
|
+
*
|
|
150
|
+
* Wire contract:
|
|
151
|
+
* - CSS font-family syntax; see DocumentSettings.font_family.
|
|
152
|
+
* - Unset (or empty string) = inherit from
|
|
153
|
+
* DocumentSettings.font_family, which itself falls back to the
|
|
154
|
+
* renderer default. Backend should emit unset rather than "".
|
|
155
|
+
* - Client-managed: the LLM never selects fonts.
|
|
156
|
+
*/
|
|
157
|
+
fontFamily?: string;
|
|
158
|
+
/**
|
|
159
|
+
* Colour for text rendered with this variant (e.g. one colour for
|
|
160
|
+
* headings, another for body copy).
|
|
161
|
+
*
|
|
162
|
+
* Wire contract:
|
|
163
|
+
* - #RGB or #RRGGBB hex.
|
|
164
|
+
* - Unset (or empty string) = inherit. Cascade: TextBlock.color →
|
|
165
|
+
* this field → DocumentSettings.text_color → renderer default.
|
|
166
|
+
* - Client-managed: the LLM picks per-item colours via TextBlock.color;
|
|
167
|
+
* variant colours are user preferences, round-tripped verbatim.
|
|
168
|
+
*/
|
|
169
|
+
color?: string;
|
|
170
|
+
/**
|
|
171
|
+
* Letter spacing (CSS letter-spacing) in px; fractional and negative
|
|
172
|
+
* values are meaningful ("-0.2" tightens display type). Unset = normal.
|
|
173
|
+
* Client-managed typography, round-tripped verbatim like font_family.
|
|
174
|
+
*/
|
|
175
|
+
letterSpacing?: number;
|
|
176
|
+
};
|
|
177
|
+
export type ColorSchemeItem = {
|
|
178
|
+
color: string;
|
|
179
|
+
description: string;
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Authored dark palette. Blocks store resolved hex, not slot references, so
|
|
183
|
+
* dark mode works by value: the renderer builds light-hex → dark-hex maps from
|
|
184
|
+
* the two palettes and rewrites the emitted HTML, pairing `pwd-N` class hooks
|
|
185
|
+
* with @media (prefers-color-scheme: dark) overrides.
|
|
186
|
+
*
|
|
187
|
+
* Absent is NOT the same as disabled-with-a-palette: without one the document
|
|
188
|
+
* declares `color-scheme: only light`, opting out of client recolouring
|
|
189
|
+
* entirely, because one light palette recoloured by a mail client collapses
|
|
190
|
+
* dark text onto a dark canvas.
|
|
191
|
+
*/
|
|
192
|
+
export type DarkColorScheme = {
|
|
193
|
+
enabled: boolean;
|
|
194
|
+
colors: Record<string, ColorSchemeItem>;
|
|
195
|
+
};
|
|
196
|
+
export type EdgeInsets = {
|
|
197
|
+
top: number;
|
|
198
|
+
right: number;
|
|
199
|
+
bottom: number;
|
|
200
|
+
left: number;
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* BlockStyles is the universal per-block visual override applied at
|
|
204
|
+
* the AiBuilderBlock level — independent of the concrete block kind.
|
|
205
|
+
* Every block kind (card, columns, group, and any future kind we add)
|
|
206
|
+
* carries it through AiBuilderBlock.styles; the client renders the
|
|
207
|
+
* frame, the LLM emits the colour overrides, and the user adjusts the
|
|
208
|
+
* rest in the editor. Per-kind layout knobs (CardStyles padding,
|
|
209
|
+
* ColumnsStyles gap, …) live on their own messages and stay
|
|
210
|
+
* independent of this one.
|
|
211
|
+
*
|
|
212
|
+
* All fields are optional. An unset field means "inherit / no
|
|
213
|
+
* override" — the client falls back to DocumentSettings.default_styles
|
|
214
|
+
* and then to its renderer default.
|
|
215
|
+
*
|
|
216
|
+
* Tri-state semantics differ between string and numeric fields:
|
|
217
|
+
* - String fields (background_color, border_color): empty string ""
|
|
218
|
+
* is equivalent to unset — clients collapse both to "inherit".
|
|
219
|
+
* Backend should emit unset rather than ""; do not rely on "" as
|
|
220
|
+
* an "explicit reset" state for colours.
|
|
221
|
+
* - Numeric fields (border_width, border_radius): explicit 0 is
|
|
222
|
+
* distinct from unset (means "no border" / "sharp corners") and
|
|
223
|
+
* is preserved across edits.
|
|
224
|
+
*
|
|
225
|
+
* LLM-managed: background_color, border_color, border_radius — the
|
|
226
|
+
* block generator may emit these via AiGenBlockStyles and the
|
|
227
|
+
* pipeline lifts them into BlockStyles.
|
|
228
|
+
* Client-managed: border_width, border_radius_css, background_image,
|
|
229
|
+
* hide_on_mobile and hide_on_desktop — the LLM never emits them (absent
|
|
230
|
+
* from its schema), the backend preserves them verbatim across edits.
|
|
231
|
+
*/
|
|
232
|
+
export type BlockStyles = {
|
|
233
|
+
/**
|
|
234
|
+
* Empty string "" is treated as unset by clients. Prefer emitting
|
|
235
|
+
* unset.
|
|
236
|
+
*/
|
|
237
|
+
backgroundColor?: string;
|
|
238
|
+
/**
|
|
239
|
+
* Empty string "" is treated as unset by clients. Prefer emitting
|
|
240
|
+
* unset.
|
|
241
|
+
*/
|
|
242
|
+
borderColor?: string;
|
|
243
|
+
/** Explicit 0 is distinct from unset and preserved. */
|
|
244
|
+
borderWidth?: number;
|
|
245
|
+
/** Explicit 0 is distinct from unset and preserved. */
|
|
246
|
+
borderRadius?: number;
|
|
247
|
+
/**
|
|
248
|
+
* CSS border-radius shorthand for per-corner rounding ("20px 20px 0px
|
|
249
|
+
* 0px"). Written by Unlayer conversion; clients render border_radius
|
|
250
|
+
* when set (user-edited uniform value wins), else this.
|
|
251
|
+
*/
|
|
252
|
+
borderRadiusCss?: string;
|
|
253
|
+
/**
|
|
254
|
+
* Block backdrop image URL over background_color (CSS cover/center/no-repeat).
|
|
255
|
+
* Reintroduces a block backdrop the old field (tag 5) removed, now client-managed
|
|
256
|
+
* (user-set, LLM never touches it). "" ≡ unset. Ignored on a CARD_LAYOUT_BACKGROUND
|
|
257
|
+
* card — that layout's own image is the backdrop.
|
|
258
|
+
*/
|
|
259
|
+
backgroundImage?: string;
|
|
260
|
+
/**
|
|
261
|
+
* Responsive visibility (Unlayer's hideMobile / hideDesktop): the emitted
|
|
262
|
+
* email hides the block via media-query classes. Client-managed.
|
|
263
|
+
*/
|
|
264
|
+
hideOnMobile?: boolean;
|
|
265
|
+
hideOnDesktop?: boolean;
|
|
266
|
+
};
|
|
267
|
+
/** Two-stop CSS linear gradient: `linear-gradient(<angle>deg, from, to)`. */
|
|
268
|
+
export type BackgroundGradient = {
|
|
269
|
+
/** #RRGGBB */
|
|
270
|
+
from: string;
|
|
271
|
+
to: string;
|
|
272
|
+
/** CSS angle in degrees (180 = top→bottom). */
|
|
273
|
+
angle: number;
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* How a columns block (or a button group) stacks on mobile (max-width media
|
|
277
|
+
* query). Unset / UNSPECIFIED = stack in DOM order (the historical behaviour).
|
|
278
|
+
*/
|
|
279
|
+
export type MobileStack = 'MOBILE_STACK_UNSPECIFIED'
|
|
280
|
+
/** Keep the items side by side on mobile (desktop widths). */
|
|
281
|
+
| 'MOBILE_STACK_NONE'
|
|
282
|
+
/** Stack in reverse order (last item on top). */
|
|
283
|
+
| 'MOBILE_STACK_REVERSE';
|
|
284
|
+
export type ColumnsStyles = {
|
|
285
|
+
/** Padding applied to each column in the columns block. */
|
|
286
|
+
columnPadding?: EdgeInsets;
|
|
287
|
+
gap?: number;
|
|
288
|
+
/** Padding applied around the columns block (outside the columns row). */
|
|
289
|
+
padding?: EdgeInsets;
|
|
290
|
+
/**
|
|
291
|
+
* Mobile (media-query) overrides of column_padding / padding. Unset =
|
|
292
|
+
* desktop value with the client's automatic mobile clamp. Client-managed,
|
|
293
|
+
* like the whole message (the LLM never sees ColumnsStyles).
|
|
294
|
+
*/
|
|
295
|
+
mobileColumnPadding?: EdgeInsets;
|
|
296
|
+
mobilePadding?: EdgeInsets;
|
|
297
|
+
mobileStack?: MobileStack;
|
|
298
|
+
};
|
|
299
|
+
export type CardStyles = {
|
|
300
|
+
/**
|
|
301
|
+
* Padding applied to the card's content (text) area on top of any
|
|
302
|
+
* document-level default. Applies to all layouts that have a content
|
|
303
|
+
* area (i.e. every layout, including NO).
|
|
304
|
+
*/
|
|
305
|
+
contentPadding?: EdgeInsets;
|
|
306
|
+
/**
|
|
307
|
+
* Mobile (media-query) override of content_padding; same contract as
|
|
308
|
+
* ColumnsStyles.mobile_* — unset = desktop value + automatic clamp.
|
|
309
|
+
*/
|
|
310
|
+
mobileContentPadding?: EdgeInsets;
|
|
311
|
+
};
|
|
312
|
+
/** Document-level image defaults; per-image fields override these. Client-managed. */
|
|
313
|
+
export type ImageStyles = {
|
|
314
|
+
/** Corner rounding, px. Unset (or 0) = square. */
|
|
315
|
+
borderRadius?: number;
|
|
316
|
+
};
|
|
317
|
+
export type DefaultStyles = {
|
|
318
|
+
common: BlockStyles;
|
|
319
|
+
columns: ColumnsStyles;
|
|
320
|
+
card: CardStyles;
|
|
321
|
+
buttonGroup: ButtonGroupStyles;
|
|
322
|
+
button: ButtonStyles;
|
|
323
|
+
/**
|
|
324
|
+
* Document-level default divider line config. Per-divider fields override
|
|
325
|
+
* these; unset here falls back to the renderer defaults (1px, scheme
|
|
326
|
+
* border, solid, 100%).
|
|
327
|
+
*/
|
|
328
|
+
divider: Divider;
|
|
329
|
+
image: ImageStyles;
|
|
330
|
+
};
|
|
331
|
+
export type DocumentSettings = {
|
|
332
|
+
width: number;
|
|
333
|
+
blockGap: number;
|
|
334
|
+
padding: EdgeInsets;
|
|
335
|
+
backgroundColor: string;
|
|
336
|
+
textColor: string;
|
|
337
|
+
defaultStyles: DefaultStyles;
|
|
338
|
+
/**
|
|
339
|
+
* Document-wide font-family override.
|
|
340
|
+
*
|
|
341
|
+
* Wire contract:
|
|
342
|
+
* - CSS font-family syntax: a comma-separated fallback chain, e.g.
|
|
343
|
+
* "Inter, system-ui, sans-serif". The backend stores it verbatim
|
|
344
|
+
* and never parses or rewrites it.
|
|
345
|
+
* - Unset (or empty string) = "no override" — the client falls
|
|
346
|
+
* back to its renderer default. Backend should emit unset rather
|
|
347
|
+
* than ""; do not rely on "" as an "explicit reset" state.
|
|
348
|
+
* - Client-managed: the LLM never sees this field and never
|
|
349
|
+
* selects fonts.
|
|
350
|
+
* - Overridden per-variant by TextVariantStyle.font_family and
|
|
351
|
+
* per-button by ButtonStyles.font_family. Cascade resolves
|
|
352
|
+
* button → variant → document → renderer default.
|
|
353
|
+
*/
|
|
354
|
+
fontFamily?: string;
|
|
355
|
+
/**
|
|
356
|
+
* Document-wide backdrop behind all blocks (email body background).
|
|
357
|
+
* Same semantics as the BlockStyles pair. Client-managed.
|
|
358
|
+
*/
|
|
359
|
+
backgroundImage?: string;
|
|
360
|
+
backgroundGradient?: BackgroundGradient;
|
|
361
|
+
/**
|
|
362
|
+
* Soft drop shadow around the content column, rendered only when a painted
|
|
363
|
+
* backdrop (background_color / gradient / image) is present.
|
|
364
|
+
*
|
|
365
|
+
* Wire contract:
|
|
366
|
+
* - Unset = shown (the default look); false is the explicit opt-out.
|
|
367
|
+
* - Client-managed: render-only chrome, the LLM never sees this field.
|
|
368
|
+
*/
|
|
369
|
+
showShadow?: boolean;
|
|
370
|
+
/**
|
|
371
|
+
* Corner rounding of the content column, px. Unset (or 0) = square.
|
|
372
|
+
* Client-managed render-only chrome, like show_shadow.
|
|
373
|
+
*/
|
|
374
|
+
borderRadius?: number;
|
|
375
|
+
};
|
|
376
|
+
export type TextBlock = {
|
|
377
|
+
variant: string;
|
|
378
|
+
text: string;
|
|
379
|
+
/**
|
|
380
|
+
* Per-text-item colour override.
|
|
381
|
+
*
|
|
382
|
+
* Wire contract:
|
|
383
|
+
* - #RGB or #RRGGBB hex.
|
|
384
|
+
* - Unset (or empty string) = inherit. Cascade: this field →
|
|
385
|
+
* DocumentSettings.text_color → renderer default. Backend should
|
|
386
|
+
* emit unset rather than "".
|
|
387
|
+
* - LLM-managed: the block generator picks an accent colour from the
|
|
388
|
+
* document's color_scheme for CTA-relevant / primary copy and leaves
|
|
389
|
+
* it empty for body text so the document default applies.
|
|
390
|
+
*/
|
|
391
|
+
color?: string;
|
|
392
|
+
/**
|
|
393
|
+
* Horizontal alignment of this text item. UNSPECIFIED = inherit (left).
|
|
394
|
+
* LLM-managed: the content generator centres headlines/captions where it
|
|
395
|
+
* fits the design; preserved verbatim on edit.
|
|
396
|
+
*/
|
|
397
|
+
align: TextAlign;
|
|
398
|
+
/**
|
|
399
|
+
* Per-language overrides of `text`, keyed by language code ("es", "de").
|
|
400
|
+
* The default language lives in `text` itself. Client-managed: the LLM
|
|
401
|
+
* writes only `text`; translations are round-tripped verbatim.
|
|
402
|
+
*/
|
|
403
|
+
translations: Record<string, string>;
|
|
404
|
+
/**
|
|
405
|
+
* Per-item font-size override in px. Unset (or 0) = inherit the variant's
|
|
406
|
+
* font_size. Client-managed: typography stays user-owned; the LLM sizes
|
|
407
|
+
* text by picking a variant. NOTE: like translations, this is lost when
|
|
408
|
+
* the LLM regenerates the block (items are rewritten wholesale).
|
|
409
|
+
*/
|
|
410
|
+
fontSize?: number;
|
|
411
|
+
/**
|
|
412
|
+
* Per-item font-family override. CSS font-family syntax (see
|
|
413
|
+
* DocumentSettings.font_family). Unset (or empty string) = inherit the
|
|
414
|
+
* variant's font_family → DocumentSettings.font_family. Client-managed.
|
|
415
|
+
*/
|
|
416
|
+
fontFamily?: string;
|
|
417
|
+
/**
|
|
418
|
+
* Background colour of this text item's box (#RGB / #RRGGBB). Unset (or
|
|
419
|
+
* empty string) = transparent — the block backdrop shows through.
|
|
420
|
+
* Client-managed: user-owned styling; like font_size, it is lost when the
|
|
421
|
+
* LLM regenerates the block (items are rewritten wholesale).
|
|
422
|
+
*/
|
|
423
|
+
backgroundColor?: string;
|
|
424
|
+
/**
|
|
425
|
+
* Mobile (media-query) font-size override in px. Unset (or 0) = the
|
|
426
|
+
* client's automatic mobile clamp of the desktop size. Client-managed;
|
|
427
|
+
* like font_size, lost when the LLM regenerates the block.
|
|
428
|
+
*/
|
|
429
|
+
mobileFontSize?: number;
|
|
430
|
+
/** Mobile alignment override. UNSPECIFIED = inherit `align` (explicit LEFT survives). Client-managed. */
|
|
431
|
+
mobileAlign: TextAlign;
|
|
432
|
+
/**
|
|
433
|
+
* Per-item letter-spacing override in px (fractional/negative meaningful;
|
|
434
|
+
* explicit 0 = "normal", overriding a variant's tracking). Unset = inherit
|
|
435
|
+
* TextVariantStyle.letter_spacing. Client-managed, like font_size.
|
|
436
|
+
*/
|
|
437
|
+
letterSpacing?: number;
|
|
438
|
+
};
|
|
439
|
+
export type ButtonActionHref = {
|
|
440
|
+
url: string;
|
|
441
|
+
};
|
|
442
|
+
/**
|
|
443
|
+
* Web-popup only: the button runs an inline JS handler instead of
|
|
444
|
+
* navigating (Unlayer's "Custom Javascript" link type). Never emitted for
|
|
445
|
+
* EMAIL documents — mail clients strip script handlers.
|
|
446
|
+
*/
|
|
447
|
+
export type ButtonActionJs = {
|
|
448
|
+
onClick: string;
|
|
449
|
+
};
|
|
450
|
+
export type ButtonAction_action_href = {
|
|
451
|
+
type: 'href';
|
|
452
|
+
data: ButtonActionHref;
|
|
453
|
+
};
|
|
454
|
+
export type ButtonAction_action_js = {
|
|
455
|
+
type: 'js';
|
|
456
|
+
data: ButtonActionJs;
|
|
457
|
+
};
|
|
458
|
+
export type ButtonAction_action = ButtonAction_action_href | ButtonAction_action_js;
|
|
459
|
+
export type ButtonAction = {
|
|
460
|
+
action: ButtonAction_action;
|
|
461
|
+
};
|
|
462
|
+
export type Button = {
|
|
463
|
+
/** Label; TextBlock.text's inline-HTML subset minus links/<br> (plain text also valid). */
|
|
464
|
+
text: string;
|
|
465
|
+
action: ButtonAction;
|
|
466
|
+
styles?: ButtonStyles;
|
|
467
|
+
/** Per-language overrides of `text`; same contract as TextBlock.translations. */
|
|
468
|
+
translations: Record<string, string>;
|
|
469
|
+
};
|
|
470
|
+
export type ButtonStyles = {
|
|
471
|
+
backgroundColor?: string;
|
|
472
|
+
textColor?: string;
|
|
473
|
+
borderRadius?: number;
|
|
474
|
+
borderColor?: string;
|
|
475
|
+
borderWidth?: number;
|
|
476
|
+
fontSize?: number;
|
|
477
|
+
/**
|
|
478
|
+
* Font-family override for button labels.
|
|
479
|
+
*
|
|
480
|
+
* Wire contract:
|
|
481
|
+
* - CSS font-family syntax; see DocumentSettings.font_family.
|
|
482
|
+
* - Unset (or empty string) = inherit. Cascade: this field →
|
|
483
|
+
* TextVariantStyle.font_family of the button's variant (if any) →
|
|
484
|
+
* DocumentSettings.font_family → renderer default. Backend
|
|
485
|
+
* should emit unset rather than "".
|
|
486
|
+
* - Client-managed: the LLM never emits this field. Button styles
|
|
487
|
+
* aren't part of the block generator's JSON schema, so this is
|
|
488
|
+
* enforced by construction.
|
|
489
|
+
*/
|
|
490
|
+
fontFamily?: string;
|
|
491
|
+
/** Label padding, px. Client-managed; unset = renderer default (8 / 16). */
|
|
492
|
+
padding?: EdgeInsets;
|
|
493
|
+
/** Width as % of the block row (1-100). Client-managed; unset = auto (fit the label). */
|
|
494
|
+
widthPercent?: number;
|
|
495
|
+
/**
|
|
496
|
+
* Mobile (media-query) override of width_percent (1-100). Client-managed;
|
|
497
|
+
* unset = the group's mobile layout decides (full width when stacked,
|
|
498
|
+
* width_percent / auto when the desktop row is kept).
|
|
499
|
+
*/
|
|
500
|
+
mobileWidthPercent?: number;
|
|
501
|
+
/** Mobile (media-query) override of padding. Client-managed; unset = desktop value. */
|
|
502
|
+
mobilePadding?: EdgeInsets;
|
|
503
|
+
};
|
|
504
|
+
export type ButtonGroup = {
|
|
505
|
+
buttons: Button[];
|
|
506
|
+
styles?: ButtonGroupStyles;
|
|
507
|
+
/**
|
|
508
|
+
* Horizontal alignment of the buttons within the group container.
|
|
509
|
+
* UNSPECIFIED = inherit from CSS / client default.
|
|
510
|
+
*/
|
|
511
|
+
align: ButtonGroupAlign;
|
|
512
|
+
};
|
|
513
|
+
export type ButtonGroupStyles = {
|
|
514
|
+
padding?: EdgeInsets;
|
|
515
|
+
backgroundColor?: string;
|
|
516
|
+
gap?: number;
|
|
517
|
+
/**
|
|
518
|
+
* How the group lays out on mobile. Unset / UNSPECIFIED = stack the
|
|
519
|
+
* buttons full-width in DOM order (the historical behaviour); NONE keeps
|
|
520
|
+
* the desktop row. Client-managed, like the whole message.
|
|
521
|
+
*/
|
|
522
|
+
mobileStack?: MobileStack;
|
|
523
|
+
/**
|
|
524
|
+
* Mobile overrides of gap / padding. Client-managed; unset = the mobile
|
|
525
|
+
* default (stacked: fixed stack gap below each button; row kept: the
|
|
526
|
+
* desktop values).
|
|
527
|
+
*/
|
|
528
|
+
mobileGap?: number;
|
|
529
|
+
mobilePadding?: EdgeInsets;
|
|
530
|
+
};
|
|
531
|
+
/**
|
|
532
|
+
* ListItem is one entry of a List. `text` carries the same inline-HTML
|
|
533
|
+
* subset as TextBlock.text (sanitised on render) so list entries can hold
|
|
534
|
+
* links / emphasis.
|
|
535
|
+
*/
|
|
536
|
+
export type ListItem = {
|
|
537
|
+
text: string;
|
|
538
|
+
/** Per-language overrides of `text`; same contract as TextBlock.translations. */
|
|
539
|
+
translations: Record<string, string>;
|
|
540
|
+
};
|
|
541
|
+
/**
|
|
542
|
+
* List is a simple list of short entries — features, benefits, steps,
|
|
543
|
+
* FAQ-style runs, terms & conditions.
|
|
544
|
+
*/
|
|
545
|
+
export type List = {
|
|
546
|
+
items: ListItem[];
|
|
547
|
+
/**
|
|
548
|
+
* true = numbered (<ol>), false/unset = bulleted (<ul>).
|
|
549
|
+
* CONTEXT for now to keep the generated schema byte-compatible with the
|
|
550
|
+
* legacy hand-written one; candidate for MODEL (the block-gen prompt
|
|
551
|
+
* already promises numbered lists).
|
|
552
|
+
*/
|
|
553
|
+
ordered: boolean;
|
|
554
|
+
/**
|
|
555
|
+
* Text variant of the entries (same registry as TextBlock.variant);
|
|
556
|
+
* unset = "normal".
|
|
557
|
+
*/
|
|
558
|
+
variant: string;
|
|
559
|
+
/**
|
|
560
|
+
* Background colour of the list's box; same contract as
|
|
561
|
+
* TextBlock.background_color. Client-managed.
|
|
562
|
+
*/
|
|
563
|
+
backgroundColor?: string;
|
|
564
|
+
};
|
|
565
|
+
export type FormFieldType =
|
|
566
|
+
/** treated as TEXT */
|
|
567
|
+
'FORM_FIELD_TYPE_UNSPECIFIED' | 'FORM_FIELD_TYPE_TEXT' | 'FORM_FIELD_TYPE_EMAIL' | 'FORM_FIELD_TYPE_PHONE' | 'FORM_FIELD_TYPE_NUMBER' | 'FORM_FIELD_TYPE_DATE' | 'FORM_FIELD_TYPE_CHECKBOX' | 'FORM_FIELD_TYPE_SELECT' | 'FORM_FIELD_TYPE_TEXTAREA' | 'FORM_FIELD_TYPE_HIDDEN' | 'FORM_FIELD_TYPE_RADIO';
|
|
568
|
+
export type FormField = {
|
|
569
|
+
/**
|
|
570
|
+
* Input name, submitted verbatim; the cloud-pages backend turns every
|
|
571
|
+
* field into a subscriber tag of the same name. Two names are special
|
|
572
|
+
* there: "email" registers an Email-platform device, "phone_number" an
|
|
573
|
+
* SMS one.
|
|
574
|
+
*/
|
|
575
|
+
name: string;
|
|
576
|
+
label: string;
|
|
577
|
+
type: FormFieldType;
|
|
578
|
+
required: boolean;
|
|
579
|
+
placeholder: string;
|
|
580
|
+
/** Choices for SELECT / RADIO / CHECKBOX-group fields. */
|
|
581
|
+
options: string[];
|
|
582
|
+
};
|
|
583
|
+
/**
|
|
584
|
+
* A subscription form (WEB_PAGE documents). Client-managed end to end in
|
|
585
|
+
* the MVP: the LLM neither sees nor generates forms; the converter and the
|
|
586
|
+
* editor own them. The emitted HTML must follow the cloud-pages submit
|
|
587
|
+
* conventions: <form method="GET"> with NO action — the hosting service
|
|
588
|
+
* rewrites the method to POST and the form posts back to the page's own
|
|
589
|
+
* URL.
|
|
590
|
+
*/
|
|
591
|
+
export type Form = {
|
|
592
|
+
fields: FormField[];
|
|
593
|
+
/** Submit button. */
|
|
594
|
+
submitLabel: string;
|
|
595
|
+
submitStyles?: ButtonStyles;
|
|
596
|
+
};
|
|
597
|
+
/**
|
|
598
|
+
* Countdown timer. Renders in email HTML as an animated GIF served by this
|
|
599
|
+
* service's public /timer/v1/countdown.gif endpoint (regenerated per open).
|
|
600
|
+
* Client-managed for the MVP: the LLM neither sees nor generates timers.
|
|
601
|
+
*/
|
|
602
|
+
export type Timer = {
|
|
603
|
+
/**
|
|
604
|
+
* Countdown target as local wall-clock time in `timezone`
|
|
605
|
+
* ("2026-12-01T00:00:00") — mirrors the Unlayer widget's
|
|
606
|
+
* endTime+timezone pair.
|
|
607
|
+
*/
|
|
608
|
+
endTime: string;
|
|
609
|
+
/** IANA zone name; empty = UTC. */
|
|
610
|
+
timezone: string;
|
|
611
|
+
/** Unit-label language (ISO 639-1); empty/unknown = "en". */
|
|
612
|
+
language: string;
|
|
613
|
+
showLabels: boolean;
|
|
614
|
+
/**
|
|
615
|
+
* Hex "#rrggbb"; empty = renderer defaults (white bg, dark digits,
|
|
616
|
+
* gray labels).
|
|
617
|
+
*/
|
|
618
|
+
backgroundColor: string;
|
|
619
|
+
digitColor: string;
|
|
620
|
+
labelColor: string;
|
|
621
|
+
/** px; 0 = 40 */
|
|
622
|
+
digitFontSize: number;
|
|
623
|
+
/** px; 0 = 16 */
|
|
624
|
+
labelFontSize: number;
|
|
625
|
+
/** Canvas size in px; 0 = derived from the font sizes. */
|
|
626
|
+
width: number;
|
|
627
|
+
height: number;
|
|
628
|
+
/**
|
|
629
|
+
* Font families carried for fidelity/round-trip; the GIF renderer
|
|
630
|
+
* currently always uses Open Sans.
|
|
631
|
+
*/
|
|
632
|
+
digitFontFamily: string;
|
|
633
|
+
labelFontFamily: string;
|
|
634
|
+
/**
|
|
635
|
+
* CSS numeric font weights. The GIF renderer maps >=600 to Open Sans
|
|
636
|
+
* Bold, anything else to Regular (only those two faces are embedded).
|
|
637
|
+
* 0 keeps the historical defaults: digits bold (700), labels regular (400).
|
|
638
|
+
*/
|
|
639
|
+
digitFontWeight: number;
|
|
640
|
+
labelFontWeight: number;
|
|
641
|
+
};
|
|
642
|
+
/**
|
|
643
|
+
* An Unlayer HTML widget. The markup is deliberately kept verbatim: unlike
|
|
644
|
+
* TextBlock.text this is a complete, user-authored HTML fragment and flattening
|
|
645
|
+
* or sanitising it would change the email. The editor previews it in a sandbox;
|
|
646
|
+
* the final email renderer emits it as-is.
|
|
647
|
+
*/
|
|
648
|
+
export type RawHtml = {
|
|
649
|
+
html: string;
|
|
650
|
+
/**
|
|
651
|
+
* Per-language overrides of `html`; same client-managed contract as
|
|
652
|
+
* TextBlock.translations.
|
|
653
|
+
*/
|
|
654
|
+
translations: Record<string, string>;
|
|
655
|
+
};
|
|
656
|
+
export type MenuItem = {
|
|
657
|
+
text: string;
|
|
658
|
+
url: string;
|
|
659
|
+
/** Unlayer stores browser targets as "_self" / "_blank". */
|
|
660
|
+
target: string;
|
|
661
|
+
translations: Record<string, string>;
|
|
662
|
+
};
|
|
663
|
+
export type MenuLayout =
|
|
664
|
+
/** renders as HORIZONTAL */
|
|
665
|
+
'MENU_LAYOUT_UNSPECIFIED' | 'MENU_LAYOUT_HORIZONTAL' | 'MENU_LAYOUT_VERTICAL';
|
|
666
|
+
/**
|
|
667
|
+
* Unlayer's stock Menu widget. These are the presentation fields exported by
|
|
668
|
+
* Unlayer itself; keeping them together avoids approximating a menu as a text
|
|
669
|
+
* run and preserves each link as a distinct editable item.
|
|
670
|
+
*/
|
|
671
|
+
export type Menu = {
|
|
672
|
+
items: MenuItem[];
|
|
673
|
+
linkColor: string;
|
|
674
|
+
textColor: string;
|
|
675
|
+
fontFamily: string;
|
|
676
|
+
fontWeight: number;
|
|
677
|
+
fontSize: number;
|
|
678
|
+
letterSpacing: number;
|
|
679
|
+
layout: MenuLayout;
|
|
680
|
+
align: TextAlign;
|
|
681
|
+
separator: string;
|
|
682
|
+
itemPadding?: EdgeInsets;
|
|
683
|
+
/** Mobile overrides; UNSPECIFIED = inherit the desktop value. Client-managed. */
|
|
684
|
+
mobileAlign: TextAlign;
|
|
685
|
+
mobileLayout: MenuLayout;
|
|
686
|
+
};
|
|
687
|
+
/**
|
|
688
|
+
* Video teaser in the content flow. Email clients can't embed players, so
|
|
689
|
+
* the HTML renders the poster (thumbnail + play-button overlay composited by
|
|
690
|
+
* this service's public /video/v1/poster.jpg endpoint) as an image linking to
|
|
691
|
+
* `url`. Client-managed for the MVP: the LLM neither sees nor generates
|
|
692
|
+
* videos; the editor derives the thumbnail from the video URL.
|
|
693
|
+
*/
|
|
694
|
+
export type Video = {
|
|
695
|
+
/** Watch-page URL the poster links to (YouTube / Vimeo / any https link). */
|
|
696
|
+
url: string;
|
|
697
|
+
/**
|
|
698
|
+
* Poster source image URL. Derived by the editor for YouTube/Vimeo links,
|
|
699
|
+
* user-replaceable. Empty = nothing is rendered in the email.
|
|
700
|
+
*/
|
|
701
|
+
thumbnailUrl: string;
|
|
702
|
+
alt: string;
|
|
703
|
+
/**
|
|
704
|
+
* Rendered width; the client Image.width idiom: "560px", or ""/"auto" =
|
|
705
|
+
* natural poster size (always capped to the content width).
|
|
706
|
+
*/
|
|
707
|
+
width: string;
|
|
708
|
+
/** UNSPECIFIED renders center (matches the image widget default). */
|
|
709
|
+
align: TextAlign;
|
|
710
|
+
/** Corner rounding, px. Unset = inherit DefaultStyles.image; explicit 0 = square. */
|
|
711
|
+
borderRadius?: number;
|
|
712
|
+
/**
|
|
713
|
+
* Composite the play button onto the poster. Unset = on; false is the
|
|
714
|
+
* explicit opt-out (same idiom as DocumentSettings.show_shadow).
|
|
715
|
+
*/
|
|
716
|
+
showPlayButton?: boolean;
|
|
717
|
+
/** Mobile alignment override. UNSPECIFIED = inherit `align`. */
|
|
718
|
+
mobileAlign: TextAlign;
|
|
719
|
+
};
|
|
720
|
+
/**
|
|
721
|
+
* Row of social-network icon links in the content flow (Unlayer's "social"
|
|
722
|
+
* widget; same SocialLink shape as the footer row). Client-managed — the
|
|
723
|
+
* LLM never emits it; icons come from the client's stock icon set.
|
|
724
|
+
*/
|
|
725
|
+
export type SocialRow = {
|
|
726
|
+
links: SocialLink[];
|
|
727
|
+
/** UNSPECIFIED renders center (Unlayer's social default). */
|
|
728
|
+
align: TextAlign;
|
|
729
|
+
/** px; 0 = client default (24) */
|
|
730
|
+
iconSize: number;
|
|
731
|
+
/** px between icons; 0 = client default (12) */
|
|
732
|
+
gap: number;
|
|
733
|
+
/** Mobile alignment override. UNSPECIFIED = inherit `align`. Client-managed. */
|
|
734
|
+
mobileAlign: TextAlign;
|
|
735
|
+
};
|
|
736
|
+
export type ContentItem_item_text = {
|
|
737
|
+
type: 'text';
|
|
738
|
+
data: TextBlock;
|
|
739
|
+
};
|
|
740
|
+
export type ContentItem_item_buttonGroup = {
|
|
741
|
+
type: 'buttonGroup';
|
|
742
|
+
data: ButtonGroup;
|
|
743
|
+
};
|
|
744
|
+
export type ContentItem_item_list = {
|
|
745
|
+
type: 'list';
|
|
746
|
+
data: List;
|
|
747
|
+
};
|
|
748
|
+
export type ContentItem_item_image = {
|
|
749
|
+
type: 'image';
|
|
750
|
+
data: Image;
|
|
751
|
+
};
|
|
752
|
+
export type ContentItem_item_form = {
|
|
753
|
+
type: 'form';
|
|
754
|
+
data: Form;
|
|
755
|
+
};
|
|
756
|
+
export type ContentItem_item_timer = {
|
|
757
|
+
type: 'timer';
|
|
758
|
+
data: Timer;
|
|
759
|
+
};
|
|
760
|
+
export type ContentItem_item_rawHtml = {
|
|
761
|
+
type: 'rawHtml';
|
|
762
|
+
data: RawHtml;
|
|
763
|
+
};
|
|
764
|
+
export type ContentItem_item_menu = {
|
|
765
|
+
type: 'menu';
|
|
766
|
+
data: Menu;
|
|
767
|
+
};
|
|
768
|
+
export type ContentItem_item_social = {
|
|
769
|
+
type: 'social';
|
|
770
|
+
data: SocialRow;
|
|
771
|
+
};
|
|
772
|
+
export type ContentItem_item_video = {
|
|
773
|
+
type: 'video';
|
|
774
|
+
data: Video;
|
|
775
|
+
};
|
|
776
|
+
export type ContentItem_item = ContentItem_item_text | ContentItem_item_buttonGroup | ContentItem_item_list | ContentItem_item_image | ContentItem_item_form | ContentItem_item_timer | ContentItem_item_rawHtml | ContentItem_item_menu | ContentItem_item_social | ContentItem_item_video;
|
|
777
|
+
export type ContentItem = {
|
|
778
|
+
/**
|
|
779
|
+
* Container padding of the item. Unset = client's default inter-item gap;
|
|
780
|
+
* set (even zeros) = used verbatim. Client-managed, hidden from the LLM.
|
|
781
|
+
*/
|
|
782
|
+
padding?: EdgeInsets;
|
|
783
|
+
/**
|
|
784
|
+
* Responsive visibility of this item (Unlayer's hideMobile / hideDesktop);
|
|
785
|
+
* same media-query contract as BlockStyles. Client-managed.
|
|
786
|
+
*/
|
|
787
|
+
hideOnMobile?: boolean;
|
|
788
|
+
hideOnDesktop?: boolean;
|
|
789
|
+
/**
|
|
790
|
+
* Mobile (media-query) override of `padding`. Unset = desktop value with
|
|
791
|
+
* the client's automatic mobile clamp. Client-managed, hidden from the LLM.
|
|
792
|
+
*/
|
|
793
|
+
mobilePadding?: EdgeInsets;
|
|
794
|
+
/**
|
|
795
|
+
* Per-recipient visibility of this item, compiled to Liquid on save.
|
|
796
|
+
* Client-managed: the model must never invent targeting rules.
|
|
797
|
+
*/
|
|
798
|
+
displayCondition?: DisplayCondition;
|
|
799
|
+
item: ContentItem_item;
|
|
800
|
+
};
|
|
801
|
+
export type Content = {
|
|
802
|
+
items: ContentItem[];
|
|
803
|
+
textPrompt?: string;
|
|
804
|
+
};
|
|
805
|
+
export type Image = {
|
|
806
|
+
prompt: string;
|
|
807
|
+
width: string;
|
|
808
|
+
height: string;
|
|
809
|
+
src: string;
|
|
810
|
+
alt: string;
|
|
811
|
+
mediaUuid: string;
|
|
812
|
+
origWidth: number;
|
|
813
|
+
origHeight: number;
|
|
814
|
+
/**
|
|
815
|
+
* Container padding of the image within its slot (card TOP image, column
|
|
816
|
+
* image). Same semantics as ContentItem.padding: client-managed, written
|
|
817
|
+
* by importers / the editor, never exposed to the LLM, round-tripped
|
|
818
|
+
* verbatim. Layouts without a discrete image box (BACKGROUND, LEFT/RIGHT
|
|
819
|
+
* fills) may ignore it.
|
|
820
|
+
*/
|
|
821
|
+
padding?: EdgeInsets;
|
|
822
|
+
/**
|
|
823
|
+
* Horizontal alignment when the image is a content item; slots ignore it.
|
|
824
|
+
* UNSPECIFIED renders as center (Unlayer's image default).
|
|
825
|
+
*/
|
|
826
|
+
align: TextAlign;
|
|
827
|
+
/**
|
|
828
|
+
* Optional click-through carried by Unlayer image widgets. Empty href =
|
|
829
|
+
* not clickable. Target is normalised to "_self" or "_blank".
|
|
830
|
+
*/
|
|
831
|
+
href: string;
|
|
832
|
+
target: string;
|
|
833
|
+
/**
|
|
834
|
+
* Per-language overrides of src / alt (localized banners); same
|
|
835
|
+
* client-managed contract as TextBlock.translations.
|
|
836
|
+
*/
|
|
837
|
+
srcTranslations: Record<string, string>;
|
|
838
|
+
altTranslations: Record<string, string>;
|
|
839
|
+
/** Corner rounding, px. Unset = inherit DefaultStyles.image; explicit 0 = square. Client-managed. */
|
|
840
|
+
borderRadius?: number;
|
|
841
|
+
/** Mobile alignment override (content items only). UNSPECIFIED = inherit `align`. Client-managed. */
|
|
842
|
+
mobileAlign: TextAlign;
|
|
843
|
+
/**
|
|
844
|
+
* Mobile width override as a CSS length ("200px" / "60%"). Unset or empty =
|
|
845
|
+
* the desktop width, scaled by the client. Only these two units are emitted;
|
|
846
|
+
* anything else is ignored rather than written into a rule. Client-managed.
|
|
847
|
+
*/
|
|
848
|
+
mobileWidth?: string;
|
|
849
|
+
/**
|
|
850
|
+
* Content-item geometry, same contract as AiBuilderColumn's slot; unset size
|
|
851
|
+
* = the item's own width. Ignored in a slot, whose container owns it.
|
|
852
|
+
*/
|
|
853
|
+
imageSize?: CardImageSize;
|
|
854
|
+
imageFit?: CardImageFit;
|
|
855
|
+
imagePosition?: CardImagePosition;
|
|
856
|
+
/** Crop box: image height as a percentage of the rendered width. Unset = natural. */
|
|
857
|
+
imageRatio?: number;
|
|
858
|
+
};
|
|
859
|
+
export type AiBuilderCard = {
|
|
860
|
+
layout: CardLayout;
|
|
861
|
+
image?: Image;
|
|
862
|
+
content: Content;
|
|
863
|
+
styles?: CardStyles;
|
|
864
|
+
/**
|
|
865
|
+
* How the image fills its slot. Relevant for layouts that have an
|
|
866
|
+
* image slot: LEFT, RIGHT, TOP, BACKGROUND. Ignored for layout NO.
|
|
867
|
+
* UNSPECIFIED lets the client fall back to its default fit.
|
|
868
|
+
*/
|
|
869
|
+
imageFit: CardImageFit;
|
|
870
|
+
/**
|
|
871
|
+
* Size of the image slot (width for LEFT/RIGHT, height for TOP, card
|
|
872
|
+
* height for BACKGROUND; ignored for NO). Client-managed: the LLM never
|
|
873
|
+
* sets it, the backend round-trips it. Unset → client default.
|
|
874
|
+
*/
|
|
875
|
+
imageSize?: CardImageSize;
|
|
876
|
+
/**
|
|
877
|
+
* Focus / anchor of the image within its slot (object-position).
|
|
878
|
+
* Client-managed: the LLM never sets it, the backend round-trips it.
|
|
879
|
+
* Unset → centre (50/50). Applies to the image layouts only.
|
|
880
|
+
*/
|
|
881
|
+
imagePosition?: CardImagePosition;
|
|
882
|
+
/**
|
|
883
|
+
* LEFT/RIGHT only: float the image so copy flows around it instead of
|
|
884
|
+
* sitting in a separate column. Unset = off, which keeps pre-wrap documents
|
|
885
|
+
* on their two-column look. Client-managed.
|
|
886
|
+
*/
|
|
887
|
+
imageWrap?: boolean;
|
|
888
|
+
/**
|
|
889
|
+
* Mobile override of image.padding for the TOP/LEFT/RIGHT slots.
|
|
890
|
+
* Client-managed.
|
|
891
|
+
*/
|
|
892
|
+
imageMobilePadding?: EdgeInsets;
|
|
893
|
+
/**
|
|
894
|
+
* LEFT/RIGHT crop box: image height as a percentage of the image column's
|
|
895
|
+
* width. Unset = natural height. Client-managed.
|
|
896
|
+
*/
|
|
897
|
+
sideImageRatio?: number;
|
|
898
|
+
};
|
|
899
|
+
export type AiBuilderColumns = {
|
|
900
|
+
columns: AiBuilderColumn[];
|
|
901
|
+
styles?: ColumnsStyles;
|
|
902
|
+
withImages: boolean;
|
|
903
|
+
};
|
|
904
|
+
export type AiBuilderColumn = {
|
|
905
|
+
image?: Image;
|
|
906
|
+
content: Content;
|
|
907
|
+
/** no_null: empty string (not JSON null) is this field's "unset" idiom. */
|
|
908
|
+
width?: string;
|
|
909
|
+
/**
|
|
910
|
+
* Container padding of this column, wrapping BOTH the image slot and the
|
|
911
|
+
* content (Unlayer column padding). Overrides ColumnsStyles.column_padding
|
|
912
|
+
* for this column when set. Client-managed; the LLM never sets it.
|
|
913
|
+
*/
|
|
914
|
+
padding?: EdgeInsets;
|
|
915
|
+
/**
|
|
916
|
+
* Slot-image geometry, same contract as the card's: size drives the WIDTH
|
|
917
|
+
* (unset = spans the cell), an explicit height or a crop ratio fixes the
|
|
918
|
+
* box, and the fit crops into it. CLIENT even though the card's image_fit is
|
|
919
|
+
* MODEL — for columns these have always been the editor's, and widening a
|
|
920
|
+
* policy later is safe while narrowing one is not.
|
|
921
|
+
*/
|
|
922
|
+
imageSize?: CardImageSize;
|
|
923
|
+
imageFit?: CardImageFit;
|
|
924
|
+
imagePosition?: CardImagePosition;
|
|
925
|
+
/** Crop box: image height as a percentage of the cell width. Unset = natural. */
|
|
926
|
+
imageRatio?: number;
|
|
927
|
+
};
|
|
928
|
+
/**
|
|
929
|
+
* AiBuilderGroup is a structural stack container. Children live as
|
|
930
|
+
* siblings in the same AiBuilderPage with their parent_id pointing at
|
|
931
|
+
* this block's id; the group itself has no copy and no image slot.
|
|
932
|
+
* Allowed in EMAIL and WEB_POPUP.
|
|
933
|
+
*
|
|
934
|
+
* Wire contract:
|
|
935
|
+
* - All fields (direction, align, margin, padding, gap) are
|
|
936
|
+
* client-managed. The backend preserves whatever the client sent
|
|
937
|
+
* verbatim across edits and emits proto-zero / unset for them on
|
|
938
|
+
* freshly generated groups.
|
|
939
|
+
* - UNSPECIFIED enums and unset optionals mean "inherit / use the
|
|
940
|
+
* document default" — see GroupDirection / GroupAlign for the
|
|
941
|
+
* render-time fallback. UNSPECIFIED is NOT wire-equivalent to the
|
|
942
|
+
* concrete default value; downstream consumers that diff documents
|
|
943
|
+
* see them as different states.
|
|
944
|
+
* - The LLM never sees these fields and never sets them; they round-
|
|
945
|
+
* trip through the backend untouched.
|
|
946
|
+
*
|
|
947
|
+
* Recommended client flow:
|
|
948
|
+
* - Default rendering (vertical stack, START alignment, document
|
|
949
|
+
* defaults for margin/padding/gap): leave UNSPECIFIED / unset.
|
|
950
|
+
* Don't normalise to a concrete enum just because that's what the
|
|
951
|
+
* user currently sees.
|
|
952
|
+
* - Send a concrete enum (VERTICAL, START, HORIZONTAL, CENTER, END)
|
|
953
|
+
* only when the user explicitly chose a non-default value, or when
|
|
954
|
+
* the user wants to pin the value against a future default change.
|
|
955
|
+
* - On reset, write back UNSPECIFIED / unset rather than the concrete
|
|
956
|
+
* default — that way the document still diffs cleanly against a
|
|
957
|
+
* freshly generated group.
|
|
958
|
+
*/
|
|
959
|
+
export type AiBuilderGroup = {
|
|
960
|
+
direction: GroupDirection;
|
|
961
|
+
/** Outer spacing around the group block. */
|
|
962
|
+
margin?: EdgeInsets;
|
|
963
|
+
/** Inner spacing between the group's frame and its children. */
|
|
964
|
+
padding?: EdgeInsets;
|
|
965
|
+
/**
|
|
966
|
+
* Spacing between adjacent children. UNSPECIFIED (0) lets the
|
|
967
|
+
* client fall back to its default. Use a non-zero value to override.
|
|
968
|
+
*/
|
|
969
|
+
gap?: number;
|
|
970
|
+
/**
|
|
971
|
+
* Alignment of children along the axis perpendicular to direction.
|
|
972
|
+
* UNSPECIFIED → render as START.
|
|
973
|
+
*/
|
|
974
|
+
align: GroupAlign;
|
|
975
|
+
/**
|
|
976
|
+
* Mobile direction override. UNSPECIFIED = inherit `direction`. Only
|
|
977
|
+
* HORIZONTAL→VERTICAL is honored: a vertical group is table rows in the
|
|
978
|
+
* email, which CSS cannot re-flow into a row. Client-managed.
|
|
979
|
+
*/
|
|
980
|
+
mobileDirection: GroupDirection;
|
|
981
|
+
};
|
|
982
|
+
/**
|
|
983
|
+
* Placeholder is a client-created empty slot meant to be replaced by
|
|
984
|
+
* the AI on a follow-up edit. The frontend builder inserts a
|
|
985
|
+
* Placeholder when the user adds a new block to the canvas without
|
|
986
|
+
* yet describing what it should be; on the next AI-edit the model
|
|
987
|
+
* replaces it with a real block kind (card / columns / content /
|
|
988
|
+
* group). Carries no rendered content of its own.
|
|
989
|
+
*/
|
|
990
|
+
export type Placeholder = {
|
|
991
|
+
/**
|
|
992
|
+
* Optional user-supplied hint about what the slot should become,
|
|
993
|
+
* e.g. "CTA section with a button" or "product card". The AI uses
|
|
994
|
+
* it as an instruction on the next edit; empty string lets the
|
|
995
|
+
* model pick freely from surrounding context.
|
|
996
|
+
*/
|
|
997
|
+
prompt?: string;
|
|
998
|
+
};
|
|
999
|
+
/** Line style for a Divider. UNSPECIFIED renders as solid. */
|
|
1000
|
+
export type DividerStyle = 'DIVIDER_STYLE_UNSPECIFIED' | 'DIVIDER_STYLE_SOLID' | 'DIVIDER_STYLE_DASHED' | 'DIVIDER_STYLE_DOTTED';
|
|
1001
|
+
/**
|
|
1002
|
+
* Divider is a horizontal separator line between sections. It carries no copy
|
|
1003
|
+
* — only line config. All fields are optional with sensible render defaults:
|
|
1004
|
+
* thickness 0 → 1px, empty color → the scheme border colour, style
|
|
1005
|
+
* UNSPECIFIED → solid, width_percent 0 → 100.
|
|
1006
|
+
*/
|
|
1007
|
+
export type Divider = {
|
|
1008
|
+
thickness: number;
|
|
1009
|
+
color: string;
|
|
1010
|
+
style: DividerStyle;
|
|
1011
|
+
widthPercent: number;
|
|
1012
|
+
};
|
|
1013
|
+
/**
|
|
1014
|
+
* SyncedBlockRef links a block subtree root back to the saved-block template it was inserted from.
|
|
1015
|
+
* Client-managed end to end: the client refreshes the subtree from the template on load,
|
|
1016
|
+
* pushes edits back via TemplatesService, and re-applies the link after AI edits.
|
|
1017
|
+
*/
|
|
1018
|
+
export type SyncedBlockRef = {
|
|
1019
|
+
/** TemplatesService code of the source template. */
|
|
1020
|
+
templateCode: string;
|
|
1021
|
+
/**
|
|
1022
|
+
* RFC3339 updated_at of the source template at last sync — the staleness check on load.
|
|
1023
|
+
* A string, not google.protobuf.Timestamp, on purpose: the value is client-opaque and lives in
|
|
1024
|
+
* documents stored as raw JSON (rpc-v2 smartcards content), where a Timestamp's generated
|
|
1025
|
+
* Date type would lie after a plain JSON revival; only the client ever parses it.
|
|
1026
|
+
*/
|
|
1027
|
+
sourceUpdatedAt: string;
|
|
1028
|
+
/**
|
|
1029
|
+
* Companion email template holding the block's rendered fragment. The email
|
|
1030
|
+
* emitter writes `{% email_content "<code>" %}` instead of the baked block,
|
|
1031
|
+
* so the sending pipeline inlines the CURRENT fragment at send time.
|
|
1032
|
+
*/
|
|
1033
|
+
fragmentCode: string;
|
|
1034
|
+
};
|
|
1035
|
+
export type DisplayConditionOp =
|
|
1036
|
+
/** Invalid: the rule is skipped at compile, as a malformed rule is today. */
|
|
1037
|
+
'DISPLAY_CONDITION_OP_UNSPECIFIED' | 'DISPLAY_CONDITION_OP_EQ' | 'DISPLAY_CONDITION_OP_NEQ' | 'DISPLAY_CONDITION_OP_GT' | 'DISPLAY_CONDITION_OP_GTE' | 'DISPLAY_CONDITION_OP_LT' | 'DISPLAY_CONDITION_OP_LTE' | 'DISPLAY_CONDITION_OP_CONTAINS' | 'DISPLAY_CONDITION_OP_NOT_CONTAINS'
|
|
1038
|
+
/** Presence checks; `value` is ignored. */
|
|
1039
|
+
| 'DISPLAY_CONDITION_OP_EXISTS' | 'DISPLAY_CONDITION_OP_NOT_EXISTS';
|
|
1040
|
+
export type DisplayConditionValueType =
|
|
1041
|
+
/** Renders as STRING. */
|
|
1042
|
+
'DISPLAY_CONDITION_VALUE_TYPE_UNSPECIFIED' | 'DISPLAY_CONDITION_VALUE_TYPE_STRING' | 'DISPLAY_CONDITION_VALUE_TYPE_NUMBER' | 'DISPLAY_CONDITION_VALUE_TYPE_BOOLEAN';
|
|
1043
|
+
export type DisplayConditionCombinator =
|
|
1044
|
+
/** Renders as AND. */
|
|
1045
|
+
'DISPLAY_CONDITION_COMBINATOR_UNSPECIFIED' | 'DISPLAY_CONDITION_COMBINATOR_AND' | 'DISPLAY_CONDITION_COMBINATOR_OR';
|
|
1046
|
+
/** What to do for a recipient missing a referenced value. UNSPECIFIED = HIDE. */
|
|
1047
|
+
export type DisplayConditionUnknown = 'DISPLAY_CONDITION_UNKNOWN_UNSPECIFIED' | 'DISPLAY_CONDITION_UNKNOWN_HIDE' | 'DISPLAY_CONDITION_UNKNOWN_SHOW';
|
|
1048
|
+
/**
|
|
1049
|
+
* Branch role in an if/elsif/else chain of ADJACENT siblings; exactly one
|
|
1050
|
+
* branch renders. UNSPECIFIED = a standalone {% if %}.
|
|
1051
|
+
*/
|
|
1052
|
+
export type DisplayConditionChain = 'DISPLAY_CONDITION_CHAIN_UNSPECIFIED' | 'DISPLAY_CONDITION_CHAIN_ELSIF' | 'DISPLAY_CONDITION_CHAIN_ELSE';
|
|
1053
|
+
export type DisplayConditionRule = {
|
|
1054
|
+
/**
|
|
1055
|
+
* Tag / dynamic-content variable exactly as the user typed it; the emitter
|
|
1056
|
+
* strips spaces to reach the Liquid binding name.
|
|
1057
|
+
*/
|
|
1058
|
+
name: string;
|
|
1059
|
+
op: DisplayConditionOp;
|
|
1060
|
+
/** Raw input, parsed per value_type at compile. Ignored for EXISTS/NOT_EXISTS. */
|
|
1061
|
+
value: string;
|
|
1062
|
+
valueType: DisplayConditionValueType;
|
|
1063
|
+
};
|
|
1064
|
+
export type DisplayCondition = {
|
|
1065
|
+
/** False keeps the rules stored without emitting anything. */
|
|
1066
|
+
enabled: boolean;
|
|
1067
|
+
combinator: DisplayConditionCombinator;
|
|
1068
|
+
rules: DisplayConditionRule[];
|
|
1069
|
+
unknown: DisplayConditionUnknown;
|
|
1070
|
+
/**
|
|
1071
|
+
* Advanced mode: a raw Liquid boolean expression. Non-empty REPLACES `rules`
|
|
1072
|
+
* and is emitted verbatim — never escaped, never validated server-side.
|
|
1073
|
+
* "" = unset.
|
|
1074
|
+
*/
|
|
1075
|
+
liquidExpr: string;
|
|
1076
|
+
chain: DisplayConditionChain;
|
|
1077
|
+
};
|
|
1078
|
+
export type ProductsSource =
|
|
1079
|
+
/** Renders as MANUAL. */
|
|
1080
|
+
'PRODUCTS_SOURCE_UNSPECIFIED' | 'PRODUCTS_SOURCE_MANUAL' | 'PRODUCTS_SOURCE_RULE' | 'PRODUCTS_SOURCE_CART';
|
|
1081
|
+
export type ProductsRuleSort =
|
|
1082
|
+
/** Invalid: the rule degrades to MANUAL, as a malformed payload does today. */
|
|
1083
|
+
'PRODUCTS_RULE_SORT_UNSPECIFIED' | 'PRODUCTS_RULE_SORT_NEWEST' | 'PRODUCTS_RULE_SORT_PRICE_ASC' | 'PRODUCTS_RULE_SORT_PRICE_DESC' | 'PRODUCTS_RULE_SORT_TITLE';
|
|
1084
|
+
/**
|
|
1085
|
+
* Catalog feed. A payload missing feed_origin, sort or a positive limit
|
|
1086
|
+
* degrades to MANUAL rather than emitting a call to a garbage URL.
|
|
1087
|
+
*/
|
|
1088
|
+
export type ProductsRule = {
|
|
1089
|
+
application: string;
|
|
1090
|
+
/** Environment-resolved feed host. "" = unset → degrades to MANUAL. */
|
|
1091
|
+
feedOrigin: string;
|
|
1092
|
+
/** "" = unset (the query parameter is omitted). */
|
|
1093
|
+
category: string;
|
|
1094
|
+
sort: ProductsRuleSort;
|
|
1095
|
+
/** Grid cells emitted, and the feed limit. <= 0 → degrades to MANUAL. */
|
|
1096
|
+
limit: number;
|
|
1097
|
+
};
|
|
1098
|
+
/** Abandoned cart of the recipient. Same degradation contract as ProductsRule. */
|
|
1099
|
+
export type ProductsCart = {
|
|
1100
|
+
application: string;
|
|
1101
|
+
feedOrigin: string;
|
|
1102
|
+
limit: number;
|
|
1103
|
+
};
|
|
1104
|
+
/**
|
|
1105
|
+
* One product row. The card design lives in ProductsData.template; elements
|
|
1106
|
+
* whose plain text is exactly "$title" / "$description" / "$price" /
|
|
1107
|
+
* "$oldPrice" bind to these fields, as do an image src of "$image" and a
|
|
1108
|
+
* button url of "$url".
|
|
1109
|
+
*/
|
|
1110
|
+
export type ProductItem = {
|
|
1111
|
+
/** Account catalog id when the row was picked from the catalog. "" = unset. */
|
|
1112
|
+
catalogId: string;
|
|
1113
|
+
image?: Image;
|
|
1114
|
+
title: string;
|
|
1115
|
+
titleTranslations: Record<string, string>;
|
|
1116
|
+
description: string;
|
|
1117
|
+
descriptionTranslations: Record<string, string>;
|
|
1118
|
+
price: string;
|
|
1119
|
+
oldPrice: string;
|
|
1120
|
+
url: string;
|
|
1121
|
+
};
|
|
1122
|
+
export type ProductsData = {
|
|
1123
|
+
source: ProductsSource;
|
|
1124
|
+
rule?: ProductsRule;
|
|
1125
|
+
cart?: ProductsCart;
|
|
1126
|
+
/**
|
|
1127
|
+
* Rendered-description cap in characters. Unset = renderer default,
|
|
1128
|
+
* explicit 0 = off.
|
|
1129
|
+
*/
|
|
1130
|
+
descriptionLimit?: number;
|
|
1131
|
+
/**
|
|
1132
|
+
* Rows for MANUAL, and the baked preview rows shown for RULE / CART outside
|
|
1133
|
+
* the save path.
|
|
1134
|
+
*/
|
|
1135
|
+
products: ProductItem[];
|
|
1136
|
+
/**
|
|
1137
|
+
* The card rendered once per row. Absent = the block falls back to the
|
|
1138
|
+
* stand-in card in the oneof.
|
|
1139
|
+
*/
|
|
1140
|
+
template?: AiBuilderCard;
|
|
1141
|
+
templateStyles?: BlockStyles;
|
|
1142
|
+
/** Grid columns, clamped 1..3 by the renderer. Unset = renderer default. */
|
|
1143
|
+
columns?: number;
|
|
1144
|
+
/** Gap between cells in px. Unset = renderer default. */
|
|
1145
|
+
gap?: number;
|
|
1146
|
+
};
|
|
1147
|
+
/**
|
|
1148
|
+
* One article row of a news repeater; the card design lives in
|
|
1149
|
+
* NewsData.template and binds these fields through "$field" refs.
|
|
1150
|
+
*/
|
|
1151
|
+
export type NewsItem = {
|
|
1152
|
+
/** Feed guid / atom id — identity for "already in the block" checks. "" = unset. */
|
|
1153
|
+
id: string;
|
|
1154
|
+
image?: Image;
|
|
1155
|
+
title: string;
|
|
1156
|
+
titleTranslations: Record<string, string>;
|
|
1157
|
+
summary: string;
|
|
1158
|
+
summaryTranslations: Record<string, string>;
|
|
1159
|
+
/**
|
|
1160
|
+
* Display string as baked ("11 Aug 2026"); published_at keeps the ISO one
|
|
1161
|
+
* so a language switch can reformat it.
|
|
1162
|
+
*/
|
|
1163
|
+
date: string;
|
|
1164
|
+
publishedAt: string;
|
|
1165
|
+
author: string;
|
|
1166
|
+
category: string;
|
|
1167
|
+
url: string;
|
|
1168
|
+
};
|
|
1169
|
+
/**
|
|
1170
|
+
* News repeater: one card template per article, baked at edit time from the
|
|
1171
|
+
* block's RSS/Atom feed.
|
|
1172
|
+
*/
|
|
1173
|
+
export type NewsData = {
|
|
1174
|
+
/** Feed the articles were taken from, kept for the panel's Refresh. */
|
|
1175
|
+
feedUrl: string;
|
|
1176
|
+
/**
|
|
1177
|
+
* Rendered-summary cap in characters. Unset = renderer default,
|
|
1178
|
+
* explicit 0 = off.
|
|
1179
|
+
*/
|
|
1180
|
+
summaryLimit?: number;
|
|
1181
|
+
items: NewsItem[];
|
|
1182
|
+
/**
|
|
1183
|
+
* The card rendered once per article. Absent = the block falls back to the
|
|
1184
|
+
* stand-in card in the oneof.
|
|
1185
|
+
*/
|
|
1186
|
+
template?: AiBuilderCard;
|
|
1187
|
+
templateStyles?: BlockStyles;
|
|
1188
|
+
/** Grid columns, clamped 1..3 by the renderer. Unset = renderer default. */
|
|
1189
|
+
columns?: number;
|
|
1190
|
+
/** Gap between cells in px. Unset = renderer default. */
|
|
1191
|
+
gap?: number;
|
|
1192
|
+
};
|
|
1193
|
+
export type AiBuilderBlock_block_card = {
|
|
1194
|
+
type: 'card';
|
|
1195
|
+
data: AiBuilderCard;
|
|
1196
|
+
};
|
|
1197
|
+
export type AiBuilderBlock_block_columns = {
|
|
1198
|
+
type: 'columns';
|
|
1199
|
+
data: AiBuilderColumns;
|
|
1200
|
+
};
|
|
1201
|
+
export type AiBuilderBlock_block_content = {
|
|
1202
|
+
type: 'content';
|
|
1203
|
+
data: Content;
|
|
1204
|
+
};
|
|
1205
|
+
export type AiBuilderBlock_block_group = {
|
|
1206
|
+
type: 'group';
|
|
1207
|
+
data: AiBuilderGroup;
|
|
1208
|
+
};
|
|
1209
|
+
export type AiBuilderBlock_block_placeholder = {
|
|
1210
|
+
type: 'placeholder';
|
|
1211
|
+
data: Placeholder;
|
|
1212
|
+
};
|
|
1213
|
+
export type AiBuilderBlock_block_divider = {
|
|
1214
|
+
type: 'divider';
|
|
1215
|
+
data: Divider;
|
|
1216
|
+
};
|
|
1217
|
+
export type AiBuilderBlock_block = AiBuilderBlock_block_card | AiBuilderBlock_block_columns | AiBuilderBlock_block_content | AiBuilderBlock_block_group | AiBuilderBlock_block_placeholder | AiBuilderBlock_block_divider;
|
|
1218
|
+
export type AiBuilderBlock = {
|
|
1219
|
+
id: string;
|
|
1220
|
+
styles?: BlockStyles;
|
|
1221
|
+
/** Synced-block link; present only on the root of an instance's subtree. */
|
|
1222
|
+
syncedRef?: SyncedBlockRef;
|
|
1223
|
+
/**
|
|
1224
|
+
* Id of the block-of-this-page that contains this one. Empty string
|
|
1225
|
+
* marks a top-level block. The parent (if set) must be a container
|
|
1226
|
+
* kind in the same AiBuilderPage. Containers may nest to arbitrary
|
|
1227
|
+
* depth; the server rejects trees deeper than 16 levels with
|
|
1228
|
+
* InvalidArgument (a sanity cap, not a product limit). Per-mode
|
|
1229
|
+
* soft hints in the planner prompt steer the model toward shallower
|
|
1230
|
+
* structures appropriate for each channel.
|
|
1231
|
+
*/
|
|
1232
|
+
parentId: string;
|
|
1233
|
+
/** Per-recipient visibility of this block, compiled to Liquid on save. */
|
|
1234
|
+
displayCondition?: DisplayCondition;
|
|
1235
|
+
/**
|
|
1236
|
+
* Products repeater. When set it OWNS the block's rendering and the oneof
|
|
1237
|
+
* holds a stand-in card, so the model (and an older editor) still sees a
|
|
1238
|
+
* valid block.
|
|
1239
|
+
*/
|
|
1240
|
+
products?: ProductsData;
|
|
1241
|
+
/**
|
|
1242
|
+
* News repeater. Same ownership contract as products: when set it OWNS the
|
|
1243
|
+
* block's rendering and the oneof holds a stand-in card.
|
|
1244
|
+
*/
|
|
1245
|
+
news?: NewsData;
|
|
1246
|
+
block: AiBuilderBlock_block;
|
|
1247
|
+
};
|
|
1248
|
+
/**
|
|
1249
|
+
* Meta carries channel-agnostic document-level metadata generated by
|
|
1250
|
+
* the meta generator. Channel-specific fields (email subject /
|
|
1251
|
+
* preheader, future popup-only / inapp-only fields) live in their own
|
|
1252
|
+
* optional sub-messages on AiBuilderDocument (EmailSettings,
|
|
1253
|
+
* PopupSettings, ...) so this message stays small and uniform across
|
|
1254
|
+
* all modes.
|
|
1255
|
+
*/
|
|
1256
|
+
export type Meta = {
|
|
1257
|
+
templateName: string;
|
|
1258
|
+
};
|
|
1259
|
+
/**
|
|
1260
|
+
* EmailType classifies a document as a commercial (marketing) message or a
|
|
1261
|
+
* transactional / relationship message. It drives footer compliance: marketing
|
|
1262
|
+
* emails must carry an unsubscribe link + postal address (CAN-SPAM), while
|
|
1263
|
+
* transactional ones (receipts, password resets, policy updates) are exempt.
|
|
1264
|
+
* The content generator classifies this from the campaign intent (see the
|
|
1265
|
+
* content-fill _meta); the client may override.
|
|
1266
|
+
*/
|
|
1267
|
+
export type EmailType =
|
|
1268
|
+
/** treated as MARKETING (safe default) */
|
|
1269
|
+
'EMAIL_TYPE_UNSPECIFIED' | 'EMAIL_TYPE_MARKETING' | 'EMAIL_TYPE_TRANSACTIONAL';
|
|
1270
|
+
export type SocialPlatform = 'SOCIAL_PLATFORM_UNSPECIFIED' | 'SOCIAL_PLATFORM_FACEBOOK' | 'SOCIAL_PLATFORM_X' | 'SOCIAL_PLATFORM_INSTAGRAM' | 'SOCIAL_PLATFORM_LINKEDIN' | 'SOCIAL_PLATFORM_YOUTUBE' | 'SOCIAL_PLATFORM_TIKTOK'
|
|
1271
|
+
/** The hosted icon set (pushwoosh.com/social-icons) already covers these. */
|
|
1272
|
+
| 'SOCIAL_PLATFORM_DISCORD' | 'SOCIAL_PLATFORM_GITHUB' | 'SOCIAL_PLATFORM_TELEGRAM' | 'SOCIAL_PLATFORM_THREADS' | 'SOCIAL_PLATFORM_WHATSAPP';
|
|
1273
|
+
export type AppStorePlatform = 'APP_STORE_PLATFORM_UNSPECIFIED' | 'APP_STORE_PLATFORM_APP_STORE' | 'APP_STORE_PLATFORM_GOOGLE_PLAY';
|
|
1274
|
+
export type NavLink = {
|
|
1275
|
+
label: string;
|
|
1276
|
+
url: string;
|
|
1277
|
+
/** Per-language overrides of `label`; same contract as MenuItem.translations. */
|
|
1278
|
+
translations: Record<string, string>;
|
|
1279
|
+
/** Per-language overrides of `url` (e.g. locale-specific Privacy Policy). */
|
|
1280
|
+
urlTranslations: Record<string, string>;
|
|
1281
|
+
};
|
|
1282
|
+
export type SocialLink = {
|
|
1283
|
+
platform: SocialPlatform;
|
|
1284
|
+
url: string;
|
|
1285
|
+
};
|
|
1286
|
+
export type AppBadge = {
|
|
1287
|
+
platform: AppStorePlatform;
|
|
1288
|
+
url: string;
|
|
1289
|
+
};
|
|
1290
|
+
/** Reusable brand footer data; never LLM-generated. */
|
|
1291
|
+
export type FooterContent = {
|
|
1292
|
+
companyName: string;
|
|
1293
|
+
address: string;
|
|
1294
|
+
copyright: string;
|
|
1295
|
+
unsubscribeLabel: string;
|
|
1296
|
+
links: NavLink[];
|
|
1297
|
+
social: SocialLink[];
|
|
1298
|
+
appBadges: AppBadge[];
|
|
1299
|
+
/**
|
|
1300
|
+
* Per-language overrides; same contract as EmailSettings.subject_translations.
|
|
1301
|
+
* URLs (links/social/badges) are the only fields shared across languages.
|
|
1302
|
+
*/
|
|
1303
|
+
unsubscribeLabelTranslations: Record<string, string>;
|
|
1304
|
+
copyrightTranslations: Record<string, string>;
|
|
1305
|
+
companyNameTranslations: Record<string, string>;
|
|
1306
|
+
addressTranslations: Record<string, string>;
|
|
1307
|
+
/**
|
|
1308
|
+
* Brand logo shown at the top of the footer. Absolute image URL (email
|
|
1309
|
+
* clients need hosted raster assets); empty = no logo.
|
|
1310
|
+
*/
|
|
1311
|
+
logoUrl?: string;
|
|
1312
|
+
/** Rendered logo width in px; 0/unset = the renderer default. */
|
|
1313
|
+
logoWidth?: number;
|
|
1314
|
+
/** Click-through URL for the logo; empty = plain image. */
|
|
1315
|
+
logoLink?: string;
|
|
1316
|
+
};
|
|
1317
|
+
/**
|
|
1318
|
+
* Per-template footer presentation. Deliberately separate from
|
|
1319
|
+
* FooterContent: FooterContent is reusable brand DATA (mirrored by the
|
|
1320
|
+
* account-level EmailFooter in rpc-v2), while these are render
|
|
1321
|
+
* preferences of one template. Client-managed; unset fields keep the
|
|
1322
|
+
* renderer defaults (muted gray, 12px, centered, underlined links).
|
|
1323
|
+
*/
|
|
1324
|
+
export type FooterStyle = {
|
|
1325
|
+
/** #RGB/#RRGGBB hex; empty = renderer default (#8A94A6). */
|
|
1326
|
+
textColor?: string;
|
|
1327
|
+
/** px; 0/unset = renderer default (12). */
|
|
1328
|
+
fontSize?: number;
|
|
1329
|
+
/** UNSPECIFIED = renderer default (center). */
|
|
1330
|
+
align: TextAlign;
|
|
1331
|
+
/**
|
|
1332
|
+
* Underline the footer's text links (nav links, unsubscribe, web-version).
|
|
1333
|
+
* Optional so absent keeps the client default (underlined).
|
|
1334
|
+
*/
|
|
1335
|
+
underlineLinks?: boolean;
|
|
1336
|
+
/**
|
|
1337
|
+
* Look of the social icons; both UNSPECIFIED reproduce today's defaults
|
|
1338
|
+
* (circle / black), so an absent value renders as it always has.
|
|
1339
|
+
*/
|
|
1340
|
+
socialIconShape: SocialIconShape;
|
|
1341
|
+
socialIconVariant: SocialIconVariant;
|
|
1342
|
+
};
|
|
1343
|
+
/**
|
|
1344
|
+
* Renderer picks a hosted icon set from these two. The value names are not
|
|
1345
|
+
* cosmetic: they are the asset path segment and filename suffix
|
|
1346
|
+
* (<base>/<shape>/<platform>-<shape>-<variant>.png), so an invented value is a
|
|
1347
|
+
* 404, not a fallback.
|
|
1348
|
+
*/
|
|
1349
|
+
export type SocialIconShape =
|
|
1350
|
+
/** Renders as CIRCLE. */
|
|
1351
|
+
'SOCIAL_ICON_SHAPE_UNSPECIFIED' | 'SOCIAL_ICON_SHAPE_CIRCLE' | 'SOCIAL_ICON_SHAPE_SQUARE' | 'SOCIAL_ICON_SHAPE_REGULAR';
|
|
1352
|
+
export type SocialIconVariant =
|
|
1353
|
+
/** Renders as BLACK. */
|
|
1354
|
+
'SOCIAL_ICON_VARIANT_UNSPECIFIED' | 'SOCIAL_ICON_VARIANT_BLACK' | 'SOCIAL_ICON_VARIANT_WHITE' | 'SOCIAL_ICON_VARIANT_COLOR';
|
|
1355
|
+
/**
|
|
1356
|
+
* EmailSettings holds the email-only document-level fields produced by
|
|
1357
|
+
* the meta generator under mode=EMAIL. Populated only when the
|
|
1358
|
+
* document was generated for the email channel; absent otherwise.
|
|
1359
|
+
*/
|
|
1360
|
+
export type EmailSettings = {
|
|
1361
|
+
subject?: string;
|
|
1362
|
+
preheader?: string;
|
|
1363
|
+
/**
|
|
1364
|
+
* Marketing vs transactional. Classified by the content generator from the
|
|
1365
|
+
* campaign intent; drives footer compliance (see EmailType). The client may
|
|
1366
|
+
* override.
|
|
1367
|
+
*/
|
|
1368
|
+
emailType: EmailType;
|
|
1369
|
+
/**
|
|
1370
|
+
* Reusable brand footer (company / address / links / …). Client-managed —
|
|
1371
|
+
* never LLM-generated. Absent = no footer configured yet.
|
|
1372
|
+
*/
|
|
1373
|
+
footer?: FooterContent;
|
|
1374
|
+
/**
|
|
1375
|
+
* Thin "View in browser" strip at the top, linking %%PW_EMAIL_WEB_LINK%%.
|
|
1376
|
+
* optional: needs presence so an unset value stays absent (client default on)
|
|
1377
|
+
* instead of collapsing to proto3 false on save.
|
|
1378
|
+
*/
|
|
1379
|
+
showWebVersion?: boolean;
|
|
1380
|
+
/**
|
|
1381
|
+
* Per-language overrides of subject / preheader; the default language
|
|
1382
|
+
* lives in the scalar fields. Client-managed, hidden from the LLM.
|
|
1383
|
+
*/
|
|
1384
|
+
subjectTranslations: Record<string, string>;
|
|
1385
|
+
preheaderTranslations: Record<string, string>;
|
|
1386
|
+
/**
|
|
1387
|
+
* Managed unsubscribe link in the document footer. It is rendered only for
|
|
1388
|
+
* marketing email. Optional so absent keeps the client default (on).
|
|
1389
|
+
*/
|
|
1390
|
+
showUnsubscribe?: boolean;
|
|
1391
|
+
/**
|
|
1392
|
+
* Footer presentation overrides for this template. Absent = renderer
|
|
1393
|
+
* defaults. Client-managed.
|
|
1394
|
+
*/
|
|
1395
|
+
footerStyle?: FooterStyle;
|
|
1396
|
+
};
|
|
1397
|
+
/**
|
|
1398
|
+
* PopupAnimation chooses how a web-popup enters / exits the
|
|
1399
|
+
* viewport. UNSPECIFIED leaves the choice to the client default
|
|
1400
|
+
* (typically NONE or FADE). Client-managed: the LLM never selects an
|
|
1401
|
+
* animation.
|
|
1402
|
+
*/
|
|
1403
|
+
export type PopupAnimation = 'POPUP_ANIMATION_UNSPECIFIED' | 'POPUP_ANIMATION_NONE' | 'POPUP_ANIMATION_FADE' | 'POPUP_ANIMATION_SLIDE';
|
|
1404
|
+
/**
|
|
1405
|
+
* PopupOverlay describes the backdrop drawn behind the popup. Both
|
|
1406
|
+
* fields are client-managed; the LLM does not see them.
|
|
1407
|
+
*/
|
|
1408
|
+
export type PopupOverlay = {
|
|
1409
|
+
/** Hex color, e.g. "#000000". Empty string = no overlay. */
|
|
1410
|
+
color: string;
|
|
1411
|
+
/** 0.0 (fully transparent) to 1.0 (opaque). */
|
|
1412
|
+
opacity: number;
|
|
1413
|
+
};
|
|
1414
|
+
/**
|
|
1415
|
+
* PopupSettings holds the web-popup channel's client-managed UI
|
|
1416
|
+
* configuration on AiBuilderDocument. Populated by the client (the
|
|
1417
|
+
* editor) and preserved across edits by the server through the same
|
|
1418
|
+
* flow as AiBuilderGroup's client-managed fields: the LLM never sees
|
|
1419
|
+
* these values, server-side conversion never writes to them, the
|
|
1420
|
+
* client owns them end-to-end.
|
|
1421
|
+
*/
|
|
1422
|
+
export type PopupSettings = {
|
|
1423
|
+
/** Corner radius of the popup container, in px. 0 = sharp corners. */
|
|
1424
|
+
borderRadius: number;
|
|
1425
|
+
/**
|
|
1426
|
+
* CSS box-shadow string, e.g. "0 4px 24px rgba(0,0,0,0.2)". Empty
|
|
1427
|
+
* string = no shadow.
|
|
1428
|
+
*/
|
|
1429
|
+
boxShadow: string;
|
|
1430
|
+
/** Backdrop overlay. Absent = no overlay. */
|
|
1431
|
+
overlay?: PopupOverlay;
|
|
1432
|
+
/** Enter / exit animation. UNSPECIFIED = client default. */
|
|
1433
|
+
animation: PopupAnimation;
|
|
1434
|
+
/**
|
|
1435
|
+
* Edge offsets in px (CSS-style positioning). Any subset of
|
|
1436
|
+
* top / bottom / left / right may be set. When none are set the
|
|
1437
|
+
* popup is centered in the viewport. Typical combinations:
|
|
1438
|
+
* - centered: leave all unset
|
|
1439
|
+
* - bottom-left: set bottom + left (with the desired gutter)
|
|
1440
|
+
* - top-right: set top + right
|
|
1441
|
+
* - full-bleed
|
|
1442
|
+
* overlay band: set both top + bottom (and/or left + right);
|
|
1443
|
+
* the client decides how to fill the gap.
|
|
1444
|
+
* Conflicting pairs (top + bottom, left + right) are permitted on
|
|
1445
|
+
* the wire — the client decides whether to stretch the popup
|
|
1446
|
+
* between the two edges or to honour one of them.
|
|
1447
|
+
*/
|
|
1448
|
+
top?: number;
|
|
1449
|
+
bottom?: number;
|
|
1450
|
+
left?: number;
|
|
1451
|
+
right?: number;
|
|
1452
|
+
};
|
|
1453
|
+
/**
|
|
1454
|
+
* AiBuilderPage groups a list of blocks under a single logical page. For
|
|
1455
|
+
* now every document has exactly one page; the structure is in place so
|
|
1456
|
+
* multi-page documents (landing pages with several screens, email digests
|
|
1457
|
+
* with tabs, etc.) can be added without a further schema break.
|
|
1458
|
+
*/
|
|
1459
|
+
export type AiBuilderPage = {
|
|
1460
|
+
id: string;
|
|
1461
|
+
blocks: AiBuilderBlock[];
|
|
1462
|
+
};
|
|
1463
|
+
export type AiBuilderDocument = {
|
|
1464
|
+
/** MVP: always one page. Consumers may assume pages[0] exists. */
|
|
1465
|
+
pages: AiBuilderPage[];
|
|
1466
|
+
settings: DocumentSettings;
|
|
1467
|
+
textVariants: Record<string, TextVariantStyle>;
|
|
1468
|
+
colorScheme: Record<string, ColorSchemeItem>;
|
|
1469
|
+
meta: Meta;
|
|
1470
|
+
/**
|
|
1471
|
+
* Email channel-specific fields. Populated only when the document
|
|
1472
|
+
* was generated for mode=EMAIL.
|
|
1473
|
+
*/
|
|
1474
|
+
emailSettings?: EmailSettings;
|
|
1475
|
+
/**
|
|
1476
|
+
* Web popup channel-specific fields. Populated only when the
|
|
1477
|
+
* document was generated for mode=WEB_POPUP.
|
|
1478
|
+
*/
|
|
1479
|
+
popupSettings?: PopupSettings;
|
|
1480
|
+
/**
|
|
1481
|
+
* Languages this document is edited in, "default" first, then language
|
|
1482
|
+
* codes in a stable order. Drives the editor's language switcher; a
|
|
1483
|
+
* language may be listed before any translation exists. Client-managed.
|
|
1484
|
+
*/
|
|
1485
|
+
languages: string[];
|
|
1486
|
+
/**
|
|
1487
|
+
* Authored dark palette, paired with color_scheme. Absent = the document
|
|
1488
|
+
* opts out of dark mode; see DarkColorScheme. Client-managed.
|
|
1489
|
+
*/
|
|
1490
|
+
darkColorScheme?: DarkColorScheme;
|
|
1491
|
+
};
|