applaunchflow 0.3.1
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/README.md +110 -0
- package/build/catalog.js +5 -0
- package/build/catalog.test.js +13 -0
- package/build/cli-core.js +20 -0
- package/build/cli-core.test.js +24 -0
- package/build/cli.js +71 -0
- package/build/client/api.js +341 -0
- package/build/http.js +178 -0
- package/build/http.test.js +111 -0
- package/build/index.js +127 -0
- package/build/prompts/register.js +66 -0
- package/build/read-receipt.test.js +36 -0
- package/build/resources/data.js +723 -0
- package/build/resources/register.js +115 -0
- package/build/social-template-previews.js +84 -0
- package/build/template-previews.js +83 -0
- package/build/tool-metadata.js +112 -0
- package/build/tool-metadata.test.js +32 -0
- package/build/tools/assets.js +311 -0
- package/build/tools/graphics.js +475 -0
- package/build/tools/keywords.js +132 -0
- package/build/tools/layouts.js +283 -0
- package/build/tools/localization.js +59 -0
- package/build/tools/mockups.js +324 -0
- package/build/tools/projects.js +113 -0
- package/build/tools/promovideo.js +199 -0
- package/build/tools/screenshots.js +307 -0
- package/build/tools/templates.js +195 -0
- package/build/tools/utils.js +212 -0
- package/build/tools/variants.js +71 -0
- package/package.json +33 -0
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
export const LAYOUT_SCHEMA_RESOURCE = {
|
|
2
|
+
appliesTo: "Both App Store screenshots and social graphics use this exact Layout shape. " +
|
|
3
|
+
"Screenshots: one layout per device size (mobile, tablet, desktop), with one screen per store screenshot. " +
|
|
4
|
+
"Social graphics: one layout per format (og, twitter, instagram_post, instagram_story, linkedin, facebook), " +
|
|
5
|
+
"each with exactly ONE screen and canvasWidth/canvasHeight set to that format's pixel dimensions. " +
|
|
6
|
+
"Everything below applies identically to both; the tools differ (transform_layout / save_layout for screenshots, " +
|
|
7
|
+
"save_graphics_format / save_graphics for social graphics), not the JSON.",
|
|
8
|
+
layout: {
|
|
9
|
+
description: "Root layout object. Screenshots: one per device size (mobile, tablet, desktop). Social graphics: one per format, holding a single screen.",
|
|
10
|
+
fields: {
|
|
11
|
+
template: { type: "string", description: "Template id used to generate this layout." },
|
|
12
|
+
platform: { type: '"ios" | "android" | "both"', description: "Target platform." },
|
|
13
|
+
canvasWidth: { type: "number", description: "Canvas width in pixels for a single screen." },
|
|
14
|
+
canvasHeight: { type: "number", description: "Canvas height in pixels for a single screen." },
|
|
15
|
+
backgroundColor: { type: "hex color", description: "Default background color for all screens." },
|
|
16
|
+
backgroundGradient: { type: "Gradient?", description: "Optional default gradient for all screens. Has type ('linear'|'radial'), colors[] and optional direction (degrees)." },
|
|
17
|
+
panoramaBackground: { type: "PanoramaBackground?", description: "A single wide image that spans across all screens as a continuous backdrop. Has imageUrl, optional storagePath, verticalOffset (0-100, 50=center), fitMode ('cover'|'contain'), blur (0-20)." },
|
|
18
|
+
themeColors: { type: "ThemeColors?", description: "Template color palette: primary, secondary, background, text, textSecondary. Used by the renderer for consistent styling." },
|
|
19
|
+
headers: { type: "HeaderNode[]?", description: "Shared header bars across screens (rare). Each has id, position, width, height, backgroundColor, zIndex." },
|
|
20
|
+
screens: { type: "Screen[]", description: "Ordered array of screens. For screenshots, each screen is one App Store screenshot. For social graphics, this array always has exactly one screen (the graphic itself)." },
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
screen: {
|
|
24
|
+
description: "A single rendered frame and all visual elements on it — one App Store screenshot, or the whole social graphic.",
|
|
25
|
+
fields: {
|
|
26
|
+
id: { type: "string", required: true, description: "Unique screen identifier." },
|
|
27
|
+
index: { type: "number", required: true, description: "Display order (0-based)." },
|
|
28
|
+
screenType: { type: "number", required: true, description: "Determines render variation (0-4 typically). Preserved on reorder." },
|
|
29
|
+
screenshots: { type: "ScreenshotNode[]", required: true, description: "Device mockups showing the app. Usually 1, sometimes 2+ for multi-phone compositions." },
|
|
30
|
+
texts: { type: "TextNode[]", required: true, description: "Text elements (headlines, subtitles, body copy)." },
|
|
31
|
+
pills: { type: "PillNode[]?", description: "Tag/button-style labels." },
|
|
32
|
+
badges: { type: "BadgeNode[]?", description: "Circular seals or count badges." },
|
|
33
|
+
blobs: { type: "BlobNode[]?", description: "Organic decorative shapes." },
|
|
34
|
+
ratings: { type: "RatingNode[]?", description: "Star rating displays with optional label." },
|
|
35
|
+
logo: { type: "LogoNode?", description: "App logo (image or text-based)." },
|
|
36
|
+
illustrations: { type: "IllustrationNode[]?", description: "Decorative images/stickers." },
|
|
37
|
+
magnifiers: { type: "MagnifierNode[]?", description: "Zoomed-in insets of a screenshot region." },
|
|
38
|
+
emojis: { type: "EmojiNode[]?", description: "Decorative emoji characters." },
|
|
39
|
+
backgroundColor: { type: "hex color?", description: "Per-screen background color override." },
|
|
40
|
+
backgroundGradient: { type: "Gradient?", description: "Per-screen gradient override." },
|
|
41
|
+
backgroundImage: { type: "object?", description: "Per-screen background image with url, optional blur (0-20), verticalOffset/horizontalOffset (0-100, 50=center)." },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
components: {
|
|
45
|
+
ScreenshotNode: {
|
|
46
|
+
description: "A device mockup displaying an app screenshot inside a phone/tablet/desktop frame.",
|
|
47
|
+
visual: "The screenshot is rendered inside a realistic device bezel. The frame style is controlled by variant3D.",
|
|
48
|
+
frameStyles: {
|
|
49
|
+
description: "Available frame styles via the variant3D field. Each gives the phone a different 3D perspective.",
|
|
50
|
+
options: {
|
|
51
|
+
none: "No frame — raw screenshot without device bezel. Use for edge-to-edge or frameless designs.",
|
|
52
|
+
flat: "Flat front-facing phone frame. Clean, straight-on view. This is the default.",
|
|
53
|
+
left: "Phone angled to the left with subtle 3D perspective.",
|
|
54
|
+
"left-2": "Phone angled more steeply to the left.",
|
|
55
|
+
right: "Phone angled to the right with subtle 3D perspective.",
|
|
56
|
+
"right-2": "Phone angled more steeply to the right.",
|
|
57
|
+
handheld: "Phone held in a hand (dark/silhouette hand).",
|
|
58
|
+
handheld2: "Phone held in a hand (medium skin tone, realistic).",
|
|
59
|
+
handheld3: "Phone held in a hand (light skin tone, realistic).",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
overflow: {
|
|
63
|
+
description: "When overflow is true, the device frame visually extends beyond the screen boundary into adjacent screens. " +
|
|
64
|
+
"This creates a seamless multi-screen effect where a phone appears to span across two screenshots. " +
|
|
65
|
+
"Commonly used with phones positioned at the edge of a screen so they partially appear on the neighboring screen.",
|
|
66
|
+
},
|
|
67
|
+
fields: {
|
|
68
|
+
id: "string — unique node id",
|
|
69
|
+
path: "string — relative storage path e.g. 'mobile/ios/1234-image.png'",
|
|
70
|
+
position: "{ x, y } — pixel position on canvas",
|
|
71
|
+
scale: "number — size multiplier (1.0 = natural size)",
|
|
72
|
+
rotation: "number — degrees",
|
|
73
|
+
zIndex: "number — stacking order",
|
|
74
|
+
overflow: "boolean? — allow device to visually extend into adjacent screens (see overflow description above)",
|
|
75
|
+
tiltAngle: "number? — tilts the entire device mockup (-30 to 30 degrees). Independent of variant3D.",
|
|
76
|
+
phoneId: "string? — phone frame id: 'iphone17' or 'googlepixel'",
|
|
77
|
+
tabletId: "string? — tablet frame id: 'ipad'",
|
|
78
|
+
desktopId: "string? — desktop frame id: 'macbook-pro-16'",
|
|
79
|
+
variant3D: "string? — frame style (see frameStyles above). Controls the 3D perspective of the device.",
|
|
80
|
+
fitMode: "'stretch' | 'cover' — how the screenshot maps inside the device frame (default: stretch)",
|
|
81
|
+
showCamera: "boolean? — show camera punch hole/notch (default true)",
|
|
82
|
+
opacity: "number? — 0-100 (default 100)",
|
|
83
|
+
shadow: "ShadowConfig? — drop shadow { color, blur, offsetX, offsetY }",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
TextNode: {
|
|
87
|
+
description: "A text element — headline, subtitle, or body copy.",
|
|
88
|
+
visual: "Rendered text with full rich-text support (bold, italic, colors, mixed fonts). Most templates use a large title + smaller subtitle per screen.",
|
|
89
|
+
fields: {
|
|
90
|
+
id: "string — unique node id",
|
|
91
|
+
richContent: "TiptapJSON — rich text content (see richContent format below)",
|
|
92
|
+
position: "{ x, y } — pixel position",
|
|
93
|
+
zIndex: "number — stacking order",
|
|
94
|
+
align: "'left' | 'center' | 'right'? — text alignment",
|
|
95
|
+
type: "'title' | 'subtitle' | 'body' | 'brands'? — semantic role",
|
|
96
|
+
lineHeight: "number? — line spacing multiplier (e.g. 1.2)",
|
|
97
|
+
width: "number? — max width for text wrapping",
|
|
98
|
+
overflow: "boolean? — allow overflow to adjacent screens",
|
|
99
|
+
rotation: "number? — degrees",
|
|
100
|
+
opacity: "number? — 0-100",
|
|
101
|
+
shadow: "ShadowConfig? — drop shadow",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
PillNode: {
|
|
105
|
+
description: "A rounded tag or button-style label.",
|
|
106
|
+
visual: "Colored rounded rectangle with text inside. Looks like a tag, chip, or CTA button. Can have an arrow icon.",
|
|
107
|
+
fields: {
|
|
108
|
+
id: "string",
|
|
109
|
+
richContent: "TiptapJSON — pill text content",
|
|
110
|
+
position: "{ x, y }",
|
|
111
|
+
zIndex: "number",
|
|
112
|
+
backgroundColor: "hex color — pill background",
|
|
113
|
+
textColor: "hex color — text inside the pill",
|
|
114
|
+
width: "number — pill width",
|
|
115
|
+
height: "number — pill height",
|
|
116
|
+
cornerRadius: "number? — defaults to fully rounded",
|
|
117
|
+
showArrow: "boolean? — show arrow icon on the right",
|
|
118
|
+
fontSize: "number?",
|
|
119
|
+
lineHeight: "number?",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
BadgeNode: {
|
|
123
|
+
description: "A circular seal or count badge.",
|
|
124
|
+
visual: "Round badge with text (e.g. '#1', '4.9★', 'NEW'). Often used for social proof.",
|
|
125
|
+
fields: {
|
|
126
|
+
id: "string",
|
|
127
|
+
text: "string — main badge text",
|
|
128
|
+
subtext: "string? — smaller secondary text",
|
|
129
|
+
size: "number — badge diameter",
|
|
130
|
+
position: "{ x, y }",
|
|
131
|
+
zIndex: "number",
|
|
132
|
+
backgroundColor: "hex color?",
|
|
133
|
+
textColor: "hex color?",
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
BlobNode: {
|
|
137
|
+
description: "An organic decorative shape.",
|
|
138
|
+
visual: "Soft, rounded organic blob shape. Used as a background accent or decorative element behind other nodes.",
|
|
139
|
+
fields: {
|
|
140
|
+
id: "string",
|
|
141
|
+
width: "number",
|
|
142
|
+
height: "number",
|
|
143
|
+
color: "hex color",
|
|
144
|
+
position: "{ x, y }",
|
|
145
|
+
zIndex: "number",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
RatingNode: {
|
|
149
|
+
description: "A star rating display.",
|
|
150
|
+
visual: "Shows filled/partial stars (e.g. ★★★★★ 4.8). Can include a text label like '12K ratings'. Stars can appear below the label.",
|
|
151
|
+
fields: {
|
|
152
|
+
id: "string",
|
|
153
|
+
rating: "number — star rating value (e.g. 4.8)",
|
|
154
|
+
label: "string? — text label (e.g. '12,345 ratings')",
|
|
155
|
+
labelColor: "hex color? — label text color",
|
|
156
|
+
size: "number? — overall size scale",
|
|
157
|
+
starsBelow: "boolean? — if true, stars appear below the label",
|
|
158
|
+
position: "{ x, y }",
|
|
159
|
+
zIndex: "number",
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
LogoNode: {
|
|
163
|
+
description: "The app logo.",
|
|
164
|
+
visual: "Shows the app icon (from uploaded logo image) or text-based logo. Usually appears on the first or last screen.",
|
|
165
|
+
fields: {
|
|
166
|
+
id: "string",
|
|
167
|
+
text: "string? — text-based logo fallback",
|
|
168
|
+
path: "string? — stored logo image path",
|
|
169
|
+
imageUrl: "string? — legacy URL field",
|
|
170
|
+
fontSize: "number? — text logo font size",
|
|
171
|
+
width: "number? — fixed width for logo box",
|
|
172
|
+
cornerRadius: "number? — rounded corners for image logos",
|
|
173
|
+
position: "{ x, y }",
|
|
174
|
+
zIndex: "number",
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
IllustrationNode: {
|
|
178
|
+
description: "A decorative image or sticker.",
|
|
179
|
+
visual: "An overlay image (PNG/SVG). Used for decorative elements like arrows, sparkles, stickers, or custom graphics.",
|
|
180
|
+
fields: {
|
|
181
|
+
id: "string",
|
|
182
|
+
path: "string? — stored illustration path",
|
|
183
|
+
imageUrl: "string? — legacy URL",
|
|
184
|
+
scale: "number? — size multiplier",
|
|
185
|
+
width: "number?",
|
|
186
|
+
height: "number?",
|
|
187
|
+
rotation: "number? — degrees",
|
|
188
|
+
tiltAngle: "number? — 3D perspective tilt (-30 to 30)",
|
|
189
|
+
primaryColor: "hex color? — SVG tint color",
|
|
190
|
+
position: "{ x, y }",
|
|
191
|
+
zIndex: "number",
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
MagnifierNode: {
|
|
195
|
+
description: "A zoomed-in inset of a screenshot region.",
|
|
196
|
+
visual: "A rounded rectangle showing a magnified portion of a screenshot. Like a 'zoom bubble' highlighting a specific UI detail.",
|
|
197
|
+
fields: {
|
|
198
|
+
id: "string",
|
|
199
|
+
screenshotId: "string — id of the screenshot node to magnify",
|
|
200
|
+
sourceRegion: "{ x, y, width, height } — relative coordinates 0-1 defining the zoom area",
|
|
201
|
+
scale: "number — zoom level (e.g. 2.0 = 200%)",
|
|
202
|
+
cornerRadius: "number — rounded corner radius in px",
|
|
203
|
+
borderWidth: "number — border thickness in px",
|
|
204
|
+
borderColor: "hex color",
|
|
205
|
+
shadowEnabled: "boolean? — drop shadow on the magnifier",
|
|
206
|
+
position: "{ x, y }",
|
|
207
|
+
zIndex: "number",
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
EmojiNode: {
|
|
211
|
+
description: "A decorative emoji character.",
|
|
212
|
+
visual: "A single emoji rendered at a given size. Used as playful accents or decorative elements.",
|
|
213
|
+
fields: {
|
|
214
|
+
id: "string",
|
|
215
|
+
emoji: "string — the emoji character(s) e.g. '🚀'",
|
|
216
|
+
size: "number — font size in pixels",
|
|
217
|
+
rotation: "number? — degrees",
|
|
218
|
+
position: "{ x, y }",
|
|
219
|
+
zIndex: "number",
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
richContentFormat: {
|
|
224
|
+
description: "Text content uses TiptapJSON (ProseMirror-compatible rich text). This is the format for TextNode.richContent and PillNode.richContent.",
|
|
225
|
+
structure: {
|
|
226
|
+
type: '"doc"',
|
|
227
|
+
attrs: "optional { defaultColor, defaultFontFamily, defaultFontSize, defaultFontWeight }",
|
|
228
|
+
content: "TiptapParagraph[] — array of paragraphs",
|
|
229
|
+
},
|
|
230
|
+
paragraph: {
|
|
231
|
+
type: '"paragraph"',
|
|
232
|
+
content: "array of text runs and hard breaks",
|
|
233
|
+
},
|
|
234
|
+
textRun: {
|
|
235
|
+
type: '"text"',
|
|
236
|
+
text: "string — the actual text content",
|
|
237
|
+
marks: "optional array of marks for styling",
|
|
238
|
+
},
|
|
239
|
+
marks: [
|
|
240
|
+
{ type: "bold", description: "Bold text" },
|
|
241
|
+
{ type: "italic", description: "Italic text" },
|
|
242
|
+
{ type: "underline", description: "Underlined text" },
|
|
243
|
+
{ type: "textStyle", attrs: "{ color?, fontFamily?, fontSize? }", description: "Inline style overrides" },
|
|
244
|
+
],
|
|
245
|
+
example: {
|
|
246
|
+
type: "doc",
|
|
247
|
+
attrs: { defaultColor: "#ffffff", defaultFontFamily: "Inter", defaultFontSize: 64, defaultFontWeight: 800 },
|
|
248
|
+
content: [
|
|
249
|
+
{
|
|
250
|
+
type: "paragraph",
|
|
251
|
+
content: [
|
|
252
|
+
{ type: "text", text: "Find Cheap", marks: [] },
|
|
253
|
+
{ type: "hardBreak" },
|
|
254
|
+
{ type: "text", text: "Flights", marks: [{ type: "textStyle", attrs: { color: "#00D4AA" } }] },
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
shadowConfig: {
|
|
261
|
+
description: "Drop shadow applied to screenshots, text, or decorative nodes.",
|
|
262
|
+
fields: {
|
|
263
|
+
color: "hex color (e.g. '#000000')",
|
|
264
|
+
blur: "number — blur radius 0-50",
|
|
265
|
+
offsetX: "number — horizontal offset -50 to 50",
|
|
266
|
+
offsetY: "number — vertical offset -50 to 50",
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
gradient: {
|
|
270
|
+
description: "Background gradient for layout or individual screens.",
|
|
271
|
+
fields: {
|
|
272
|
+
type: "'linear' | 'radial'",
|
|
273
|
+
colors: "string[] — array of hex colors (min 2)",
|
|
274
|
+
direction: "number? — angle in degrees (for linear gradients)",
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
export const TRANSFORM_SCHEMA_RESOURCE = {
|
|
279
|
+
operations: [
|
|
280
|
+
"update_node",
|
|
281
|
+
"delete_node",
|
|
282
|
+
"add_node",
|
|
283
|
+
"reorder",
|
|
284
|
+
"replace_color",
|
|
285
|
+
],
|
|
286
|
+
nodeTypes: [
|
|
287
|
+
"screen",
|
|
288
|
+
"header",
|
|
289
|
+
"panoramaBackground",
|
|
290
|
+
"screenshot",
|
|
291
|
+
"text",
|
|
292
|
+
"pill",
|
|
293
|
+
"badge",
|
|
294
|
+
"blob",
|
|
295
|
+
"rating",
|
|
296
|
+
"logo",
|
|
297
|
+
"illustration",
|
|
298
|
+
"magnifier",
|
|
299
|
+
"emoji",
|
|
300
|
+
"backgroundImage",
|
|
301
|
+
],
|
|
302
|
+
target: {
|
|
303
|
+
nodeType: "required for precise updates",
|
|
304
|
+
nodeId: "optional specific node identifier",
|
|
305
|
+
selector: {
|
|
306
|
+
format: "string — one of the following patterns",
|
|
307
|
+
examples: [
|
|
308
|
+
"screen:0 — target screen by index",
|
|
309
|
+
"screen_1 — target screen by index (alternate syntax)",
|
|
310
|
+
"screenId:my-screen-id — target screen by its id field (use this for newly added screens)",
|
|
311
|
+
"all_headers — target all headers",
|
|
312
|
+
"all_texts — target all texts",
|
|
313
|
+
],
|
|
314
|
+
warning: "Do NOT use '#' prefix or any other format. Invalid selectors silently match ALL screens.",
|
|
315
|
+
},
|
|
316
|
+
screens: "array of screen indexes or 'all' — preferred way to target screens by index",
|
|
317
|
+
},
|
|
318
|
+
notes: [
|
|
319
|
+
"Default to layouts: ['mobile']. Only include tablet/desktop if the user explicitly asks.",
|
|
320
|
+
"Use get_layout first whenever the edit changes composition or should closely match the current styling.",
|
|
321
|
+
"add_node expects changes.node or changes to contain a full node payload with an id.",
|
|
322
|
+
"The backend validates the resulting layout before saving. Incomplete nodes such as text without position or screenshot without placement will be rejected.",
|
|
323
|
+
"When adding screens and then populating them, use two separate transform_layout calls: first add the empty screens, then target them by id using selector 'screenId:<id>' to add text and screenshot nodes.",
|
|
324
|
+
"Ensure added or moved elements do not overlap other elements on the same screen. Text must not cover screenshots and vice versa.",
|
|
325
|
+
"reorder expects changes.order or changes.nodeIds with node ids in the desired order.",
|
|
326
|
+
],
|
|
327
|
+
};
|
|
328
|
+
export const VIDEO_CONFIG_SCHEMA_RESOURCE = {
|
|
329
|
+
description: "Remotion VideoConfig — the full promo video document returned by get_promo_video / generate_promo_video and " +
|
|
330
|
+
"accepted by update_promo_video. update_promo_video is a whole-config replace: read the current config, mutate " +
|
|
331
|
+
"the fields you need, send the entire object back. Unknown extra keys are stripped by validation, so never hand-build " +
|
|
332
|
+
"a config from scratch when an existing one can be fetched.",
|
|
333
|
+
videoConfig: {
|
|
334
|
+
description: "Root object.",
|
|
335
|
+
fields: {
|
|
336
|
+
version: { type: "1 | 2 | null", description: "Config format version. Preserve whatever the fetched config has." },
|
|
337
|
+
theme: { type: "VideoTheme", required: true, description: "Colors, optional background gradient, and typography. See theme below." },
|
|
338
|
+
scenes: { type: "Scene[]", required: true, description: "Ordered scenes, at least one. Each scene is a discriminated union on `type`. See sceneTypes below." },
|
|
339
|
+
duration: { type: "number | null", description: "Total video length in seconds. Normally the sum of scene durations; leave as-is unless changing pacing deliberately." },
|
|
340
|
+
audio: { type: "AudioTrack? | null", description: "Single background music track spanning the whole video. See audio below." },
|
|
341
|
+
phoneId: { type: "string? | null", description: 'Device frame model, e.g. "iphone17" or "googlepixel". Defaults to iphone17.' },
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
theme: {
|
|
345
|
+
description: "Global look. Individual scenes can override text colors per element via TextStyle.color.",
|
|
346
|
+
fields: {
|
|
347
|
+
"colors.primary": { type: "hex color", required: true },
|
|
348
|
+
"colors.secondary": { type: "hex color", required: true },
|
|
349
|
+
"colors.background": { type: "hex color", required: true },
|
|
350
|
+
"colors.text": { type: "hex color", required: true },
|
|
351
|
+
"colors.textSecondary": { type: "hex color", required: true },
|
|
352
|
+
backgroundGradient: { type: "object? | null", description: 'type: "linear" | "radial", colors: string[] (min 2), direction?: number (0-360 degrees).' },
|
|
353
|
+
"typography.fontFamily": { type: "string", required: true },
|
|
354
|
+
"typography.titleSize": { type: "number", required: true, description: "Base title font size in px at 1080x1920." },
|
|
355
|
+
"typography.subtitleSize": { type: "number", required: true },
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
sceneCommon: {
|
|
359
|
+
description: "Fields available on EVERY scene regardless of `type`. Sit at the scene root, next to `type` and `content`.",
|
|
360
|
+
fields: {
|
|
361
|
+
type: { type: '"hook" | "feature" | "text-only" | "closeup" | "multi-phone" | "cta"', required: true, description: "Discriminator. Determines the shape of `content`." },
|
|
362
|
+
content: { type: "object", required: true, description: "Type-specific payload. See sceneTypes below — sending the wrong content shape for a type fails validation." },
|
|
363
|
+
duration: { type: "number | null", description: "Scene length in seconds, 1-15." },
|
|
364
|
+
choreography: { type: "string | null", description: "Choreography preset id. See choreographyPresets below." },
|
|
365
|
+
kenBurns: { type: "KenBurnsConfig? | null", description: "Slow camera move on the scene. See kenBurns below." },
|
|
366
|
+
atmosphere: { type: "object? | null", description: "particles, gradient, glare (each boolean|null) and intensity ('subtle'|'medium'|'dramatic'|null)." },
|
|
367
|
+
transition: { type: "object? | null", description: "Transition INTO this scene: { type: choreography id, duration: frames|null }. Use a transition-category preset." },
|
|
368
|
+
animationSpeed: { type: "number? | null", description: "Scene-wide animation speed multiplier, 0.5-2." },
|
|
369
|
+
illustrations: { type: "IllustrationOverlay[]?", description: "Decorative images. Each: url, storagePath?, x/y (0-100, %), scale (0.1-4), opacity (0-1), rotation (-180..180)." },
|
|
370
|
+
textOverlays: { type: "TextOverlay[]?", description: "Free-form rich text placed anywhere. Each: richContent (Tiptap doc JSON), text? (plain mirror), x/y (0-100), scale (0.1-4), rotation (-180..180), opacity (0-1), width (5-100, wrap width as % of frame), textAlign, fontSizeScale (0.2-4), textAnimation." },
|
|
371
|
+
devices: { type: "SceneDevice[]?", description: "Extra device frames on any scene type, max 12. See sceneDevice below." },
|
|
372
|
+
hiddenElements: { type: "string[]?", description: "Element ids to hide on this scene." },
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
sceneTypes: {
|
|
376
|
+
hook: {
|
|
377
|
+
description: "Opening brand beat: logo + app name + tagline.",
|
|
378
|
+
content: {
|
|
379
|
+
appName: "string | null",
|
|
380
|
+
appNameImageUrl: "string? | null — pre-rendered wordmark image, used instead of appName text when set.",
|
|
381
|
+
tagline: "string | null",
|
|
382
|
+
logoUrl: "string | null",
|
|
383
|
+
showLogo: "boolean | null",
|
|
384
|
+
appNameStyle: "TextStyle? | null",
|
|
385
|
+
taglineStyle: "TextStyle? | null",
|
|
386
|
+
logoPosition: "{ x, y } 0-100 | null",
|
|
387
|
+
logoScale: "number 0.5-2 | null",
|
|
388
|
+
logoPadding: "number 0-0.45 | null — padding inside the logo card; lower is tighter around the icon.",
|
|
389
|
+
textAnimation: "TextAnimation | null",
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
feature: {
|
|
393
|
+
description: "One phone plus a title/subtitle — the workhorse scene.",
|
|
394
|
+
content: {
|
|
395
|
+
screenshotIndex: "number >= 0, required — index into the project's uploaded screenshots.",
|
|
396
|
+
video: "SceneVideo? | null — plays inside the device frame instead of the still. See sceneVideo below.",
|
|
397
|
+
frameVariant: '"flat" | "right" | "left" | null',
|
|
398
|
+
title: "string, required",
|
|
399
|
+
subtitle: "string | null",
|
|
400
|
+
titleStyle: "TextStyle? | null",
|
|
401
|
+
subtitleStyle: "TextStyle? | null",
|
|
402
|
+
phonePosition: "{ x, y } 0-100 | null",
|
|
403
|
+
phoneScale: "number 0.5-2.5 | null",
|
|
404
|
+
textAnimation: "TextAnimation | null",
|
|
405
|
+
disablePhoneOut: "boolean? | null — suppress the phone's zoom-through exit at the end of the scene.",
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
"text-only": {
|
|
409
|
+
description: "Full-frame statement card, no device.",
|
|
410
|
+
content: {
|
|
411
|
+
headline: "string, required",
|
|
412
|
+
subheadline: "string | null",
|
|
413
|
+
headlineStyle: "TextStyle? | null",
|
|
414
|
+
subheadlineStyle: "TextStyle? | null",
|
|
415
|
+
textAnimation: "TextAnimation | null",
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
closeup: {
|
|
419
|
+
description: "Zooms into a region of one screenshot to highlight a detail.",
|
|
420
|
+
content: {
|
|
421
|
+
screenshotIndex: "number >= 0, required",
|
|
422
|
+
video: "SceneVideo? | null",
|
|
423
|
+
focusRegion: "{ x, y (0-100), width, height (1-100) } | null — the region of the screenshot to magnify, in %.",
|
|
424
|
+
focusPosition: "{ x, y } 0-100 | null — where the zoomed window sits on screen. Defaults to centered/lower-third.",
|
|
425
|
+
caption: "string | null",
|
|
426
|
+
captionStyle: "TextStyle? | null",
|
|
427
|
+
frameVariant: '"flat" | "right" | "left" | null',
|
|
428
|
+
phonePosition: "{ x, y } 0-100 | null",
|
|
429
|
+
phoneScale: "number 0.5-2.5 | null",
|
|
430
|
+
textAnimation: "TextAnimation | null",
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
"multi-phone": {
|
|
434
|
+
description: "2-6 phones arranged together. Per-phone arrays are positional — index i describes screenshotIndexes[i].",
|
|
435
|
+
content: {
|
|
436
|
+
screenshotIndexes: "number[], 1-6 entries, required",
|
|
437
|
+
layout: '"cascade" | "fan" | "side-by-side" | "custom", required',
|
|
438
|
+
title: "string | null",
|
|
439
|
+
titleStyle: "TextStyle? | null",
|
|
440
|
+
frameVariants: '("flat"|"left"|"right")[] max 6 | null',
|
|
441
|
+
phonePositions: "({ x, y } | null)[] max 6 | null — manual overrides from dragging.",
|
|
442
|
+
phoneScales: "number[] (0.4-2.5) max 6 | null",
|
|
443
|
+
phoneRotations: "number[] (-45..45) max 6 | null",
|
|
444
|
+
textAnimation: "TextAnimation | null",
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
cta: {
|
|
448
|
+
description: "Closing download call-to-action with store badges.",
|
|
449
|
+
content: {
|
|
450
|
+
appName: "string | null",
|
|
451
|
+
appNameImageUrl: "string? | null",
|
|
452
|
+
downloadText: "string | null",
|
|
453
|
+
badge: '"appStore" | "googlePlay" | "both" | "none" | null',
|
|
454
|
+
badgePosition: "{ x, y } 0-100 | null — centered in flow when unset.",
|
|
455
|
+
badgeScale: "number 0.3-2 | null",
|
|
456
|
+
logoUrl: "string? | null",
|
|
457
|
+
showLogo: "boolean? | null",
|
|
458
|
+
logoPosition: "{ x, y } 0-100 | null",
|
|
459
|
+
logoScale: "number 0.5-2 | null",
|
|
460
|
+
logoPadding: "number 0-0.45 | null",
|
|
461
|
+
appNameStyle: "TextStyle? | null",
|
|
462
|
+
downloadTextStyle: "TextStyle? | null",
|
|
463
|
+
textAnimation: "TextAnimation | null",
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
textStyle: {
|
|
468
|
+
description: "Per-text-element styling. Every field is optional; omit or null to inherit the theme. Applies to appNameStyle, taglineStyle, titleStyle, subtitleStyle, headlineStyle, subheadlineStyle, captionStyle, downloadTextStyle.",
|
|
469
|
+
fields: {
|
|
470
|
+
fontSizeScale: { type: "number 0.5-2", description: "Multiplier on the theme's title/subtitle size." },
|
|
471
|
+
fontWeight: { type: '"light" | "regular" | "medium" | "bold" | "black"' },
|
|
472
|
+
color: { type: "hex color" },
|
|
473
|
+
textAlign: { type: '"left" | "center" | "right"' },
|
|
474
|
+
position: { type: "{ x, y } 0-100", description: "Manual placement as a % of the frame." },
|
|
475
|
+
animationSpeed: { type: "number 0.5-2" },
|
|
476
|
+
richContent: { type: "Tiptap doc JSON?", description: "Rich version of this field's text. When it carries real formatting marks the renderer draws it statically with a fade; otherwise the plain string + animation path is used. The plain field stays canonical." },
|
|
477
|
+
textAnimation: { type: 'TextAnimation | "none" | null', description: "Per-element override. null/absent inherits the scene's content.textAnimation; \"none\" forces no animation." },
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
textAnimations: [
|
|
481
|
+
"word-reveal",
|
|
482
|
+
"fade",
|
|
483
|
+
"character-cascade",
|
|
484
|
+
"scale-emphasis",
|
|
485
|
+
"typewriter",
|
|
486
|
+
"word-highlight",
|
|
487
|
+
],
|
|
488
|
+
sceneDevice: {
|
|
489
|
+
description: "An extra device frame layered onto any scene (scene.devices[]). Independent of the scene type's own phone.",
|
|
490
|
+
fields: {
|
|
491
|
+
screenshotIndex: { type: "number >= 0", required: true },
|
|
492
|
+
frameVariant: { type: '"flat" | "right" | "left"', description: "Default 'flat'." },
|
|
493
|
+
position: { type: "{ x, y } 0-100", description: "Default { x: 50, y: 60 }." },
|
|
494
|
+
scale: { type: "number 0.2-4", description: "Default 1." },
|
|
495
|
+
rotation: { type: "number -180..180", description: "Default 0." },
|
|
496
|
+
opacity: { type: "number 0-1", description: "Default 1." },
|
|
497
|
+
animation: { type: '"none" | "fade" | "feature-pop" | "float-up" | "slide-left" | "slide-right" | "zoom-in" | "drift" | "pulse" | "cascade"', description: "Combined in/out preset. Default 'fade'." },
|
|
498
|
+
animationDelay: { type: "number 0-3", description: "Seconds. Default 0." },
|
|
499
|
+
},
|
|
500
|
+
legacyFields: "outAnimation / outAnimationDelay exist only for configs saved before in/out were merged. Do not set them on new edits.",
|
|
501
|
+
},
|
|
502
|
+
sceneVideo: {
|
|
503
|
+
description: "Optional clip that plays inside the device frame on feature/closeup scenes, replacing the still screenshot. Trimmed and looped to the scene duration.",
|
|
504
|
+
fields: {
|
|
505
|
+
storagePath: { type: "string? | null", description: 'Authoritative project-relative path, e.g. "promo-media/123-clip.webm".' },
|
|
506
|
+
url: { type: "string? | null", description: "Ephemeral signed URL, re-signed on load/export. Do not fabricate." },
|
|
507
|
+
durationSeconds: { type: "number? | null", description: "Source clip length, used to loop short clips." },
|
|
508
|
+
trimStartSeconds: { type: "number? | null", description: "Start offset into the source clip." },
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
audio: {
|
|
512
|
+
description: "Single background music track for the whole video (videoConfig.audio).",
|
|
513
|
+
fields: {
|
|
514
|
+
storagePath: { type: "string", required: true, description: 'Project-relative path, e.g. "promo-media/123-track.mp3". Authoritative.' },
|
|
515
|
+
url: { type: "string? | null", description: "Ephemeral signed URL, re-signed on load/export." },
|
|
516
|
+
fileName: { type: "string? | null", description: "Original filename, shown in the editor." },
|
|
517
|
+
volume: { type: "number 0-1", description: "Default 0.8." },
|
|
518
|
+
fadeInSeconds: { type: "number 0-10", description: "Default 0." },
|
|
519
|
+
fadeOutSeconds: { type: "number 0-10", description: "Default 0." },
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
kenBurns: {
|
|
523
|
+
description: "Slow camera drift over the scene. Set `preset` for a named move, or scaleStart/scaleEnd + pan* for a manual one.",
|
|
524
|
+
fields: {
|
|
525
|
+
enabled: { type: "boolean?" },
|
|
526
|
+
preset: { type: '"slow-zoom-in" | "slow-zoom-out" | "pan-left" | "pan-right" | "zoom-in-pan-right" | "zoom-out-pan-left" | "subtle-drift" | null' },
|
|
527
|
+
intensity: { type: '"subtle" | "moderate" | "dramatic" | null' },
|
|
528
|
+
direction: { type: '"zoom-in" | "zoom-out" | "pan-left" | "pan-right" | null' },
|
|
529
|
+
scaleStart: { type: "number | null" },
|
|
530
|
+
scaleEnd: { type: "number | null" },
|
|
531
|
+
panStartX: { type: "number | null" },
|
|
532
|
+
panEndX: { type: "number | null" },
|
|
533
|
+
panStartY: { type: "number | null" },
|
|
534
|
+
panEndY: { type: "number | null" },
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
choreographyPresets: {
|
|
538
|
+
description: "Valid ids for scene.choreography and scene.transition.type. Use an entrance/multi-phone/kenBurns id for `choreography`; use a transition id for `transition.type`. Unknown ids fall back to the default motion.",
|
|
539
|
+
entrance: ["hero-center", "slide-left", "slide-right", "drop-bounce", "zoom-through", "rotate-in", "float-up"],
|
|
540
|
+
multiPhone: ["cascade-left", "cascade-right", "fan-out", "side-by-side"],
|
|
541
|
+
kenBurns: ["slow-zoom-in", "slow-zoom-out", "pan-left", "pan-right"],
|
|
542
|
+
transition: ["morph-scale", "cross-fade", "wipe-left", "push-up"],
|
|
543
|
+
},
|
|
544
|
+
notes: [
|
|
545
|
+
"Coordinates (x, y, position, focusRegion) are percentages of the frame, 0-100, where 50 is centered — never pixels.",
|
|
546
|
+
"screenshotIndex / screenshotIndexes point at the project's uploaded screenshots in upload order. Out-of-range indexes render blank.",
|
|
547
|
+
"Do not invent storagePath or url values for video/audio/logo assets. Upload via upload_asset, or reuse the exact values from get_promo_video.",
|
|
548
|
+
"Values outside the documented ranges are rejected by validation — the whole update fails, not just that field.",
|
|
549
|
+
"Adding or removing scenes changes the total runtime. Update videoConfig.duration to match, or the export and the preview disagree.",
|
|
550
|
+
],
|
|
551
|
+
};
|
|
552
|
+
export const SUPPORTED_LANGUAGES = [
|
|
553
|
+
"en",
|
|
554
|
+
"es",
|
|
555
|
+
"fr",
|
|
556
|
+
"de",
|
|
557
|
+
"it",
|
|
558
|
+
"pt",
|
|
559
|
+
"pt-BR",
|
|
560
|
+
"ja",
|
|
561
|
+
"ko",
|
|
562
|
+
"zh-CN",
|
|
563
|
+
"zh-TW",
|
|
564
|
+
"nl",
|
|
565
|
+
"ru",
|
|
566
|
+
"ar",
|
|
567
|
+
"tr",
|
|
568
|
+
"pl",
|
|
569
|
+
"sv",
|
|
570
|
+
"no",
|
|
571
|
+
"da",
|
|
572
|
+
"fi",
|
|
573
|
+
"cs",
|
|
574
|
+
"hu",
|
|
575
|
+
"ro",
|
|
576
|
+
"uk",
|
|
577
|
+
];
|
|
578
|
+
export const SUPPORTED_DEVICES = {
|
|
579
|
+
phone: [
|
|
580
|
+
{ id: "iphone17", label: "iPhone 17 Pro Max", aspectRatio: 1320 / 2868 },
|
|
581
|
+
{ id: "googlepixel", label: "Google Pixel", aspectRatio: 1080 / 2400 },
|
|
582
|
+
],
|
|
583
|
+
tablet: [
|
|
584
|
+
{ id: "ipad", label: 'iPad Pro 12.9"', aspectRatio: 2048 / 2732 },
|
|
585
|
+
],
|
|
586
|
+
desktop: [
|
|
587
|
+
{
|
|
588
|
+
id: "macbook-pro-16",
|
|
589
|
+
label: 'MacBook Pro 16"',
|
|
590
|
+
aspectRatio: 2880 / 1800,
|
|
591
|
+
},
|
|
592
|
+
],
|
|
593
|
+
};
|
|
594
|
+
export const PROJECT_CREATION_WIZARD_RESOURCE = {
|
|
595
|
+
title: "Create Project Wizard",
|
|
596
|
+
matchesUi: "dashboard upload flow",
|
|
597
|
+
steps: [
|
|
598
|
+
{
|
|
599
|
+
order: 0,
|
|
600
|
+
id: "entry-branch",
|
|
601
|
+
label: "Create Or Edit",
|
|
602
|
+
required: true,
|
|
603
|
+
prompt: "Before templates or screenshot generation, branch first: create a new app or edit an existing project.",
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
order: 1,
|
|
607
|
+
id: "platform",
|
|
608
|
+
label: "Default Platform",
|
|
609
|
+
required: true,
|
|
610
|
+
prompt: "Ask whether the project should start on iOS or Android. Only offer both if the user explicitly asks for it.",
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
order: 2,
|
|
614
|
+
id: "store-import",
|
|
615
|
+
label: "Import from App Store",
|
|
616
|
+
required: false,
|
|
617
|
+
prompt: "Offer optional App Store import by URL or numeric Apple app id before manual entry.",
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
order: 3,
|
|
621
|
+
id: "app-name",
|
|
622
|
+
label: "App Name",
|
|
623
|
+
required: true,
|
|
624
|
+
prompt: "Confirm the final app name after import or manual entry. Do not create the project without this.",
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
order: 4,
|
|
628
|
+
id: "category",
|
|
629
|
+
label: "App Category",
|
|
630
|
+
required: false,
|
|
631
|
+
prompt: "Offer the category field as optional.",
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
order: 5,
|
|
635
|
+
id: "logo",
|
|
636
|
+
label: "Logo",
|
|
637
|
+
required: false,
|
|
638
|
+
prompt: "Offer an optional logo step. Store import can prefill the icon; manual flow can skip it.",
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
order: 6,
|
|
642
|
+
id: "app-context",
|
|
643
|
+
label: "App Context",
|
|
644
|
+
required: false,
|
|
645
|
+
prompt: "Offer optional app context/description to improve downstream screenshot and ASO generation.",
|
|
646
|
+
},
|
|
647
|
+
],
|
|
648
|
+
defaults: {
|
|
649
|
+
platform: "ios",
|
|
650
|
+
defaultDeviceType: "phone",
|
|
651
|
+
},
|
|
652
|
+
createProjectRule: "Call create_project only after the user has chosen the create-new-app path, plus platform and app name are known.",
|
|
653
|
+
nextStepAfterCreate: "Upload screenshots",
|
|
654
|
+
};
|
|
655
|
+
export const WORKFLOW_GUIDE_RESOURCE = {
|
|
656
|
+
title: "Preferred MCP Workflows",
|
|
657
|
+
principle: "Prefer direct execution for concrete requests. Ask follow-up questions only when needed to avoid a materially wrong result.",
|
|
658
|
+
workflows: [
|
|
659
|
+
{
|
|
660
|
+
name: "Browse templates visually",
|
|
661
|
+
preferredSteps: [
|
|
662
|
+
"browse_templates",
|
|
663
|
+
],
|
|
664
|
+
notes: "Use browse_templates when the user needs to pick a template from the hosted visual gallery. Keep the full template catalog available unless the user asks for a shortlist.",
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
name: "Start screenshot work",
|
|
668
|
+
preferredSteps: [
|
|
669
|
+
"create_project",
|
|
670
|
+
"list_projects",
|
|
671
|
+
"get_project",
|
|
672
|
+
],
|
|
673
|
+
notes: "If no project is in scope yet, start by branching to create a new app or edit an existing one. Do not begin with template browsing before a project path is chosen.",
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
name: "Prepare and apply a personalized screenshot style",
|
|
677
|
+
preferredSteps: [
|
|
678
|
+
"list_source_screenshots",
|
|
679
|
+
"prepare_screenshot_styles",
|
|
680
|
+
"browse_templates",
|
|
681
|
+
"apply_screenshot_style",
|
|
682
|
+
],
|
|
683
|
+
notes: "Select 3-7 real screenshots in story order and prepare the personalized catalog once. Browse only the returned template ids, then apply the selection from the catalog cache. The new variant includes phone, tablet, and desktop without a second AI call.",
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
name: "Prepare and apply personalized social graphics",
|
|
687
|
+
preferredSteps: [
|
|
688
|
+
"list_source_screenshots",
|
|
689
|
+
"prepare_social_graphics_styles",
|
|
690
|
+
"browse_social_templates",
|
|
691
|
+
"apply_social_graphics_style",
|
|
692
|
+
],
|
|
693
|
+
notes: "Prepare every social template across all six formats once, let the user choose visually, and apply that cached selection as a new variant.",
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
name: "Edit current layout directly",
|
|
697
|
+
preferredSteps: [
|
|
698
|
+
"get_layout",
|
|
699
|
+
"transform_layout",
|
|
700
|
+
],
|
|
701
|
+
notes: "Use get_layout first for every direct layout edit. Treat the read as mandatory, then transform the layout, then return the editor URL so the user can review visually.",
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
name: "Add new screens to current layout",
|
|
705
|
+
preferredSteps: [
|
|
706
|
+
"get_layout",
|
|
707
|
+
"transform_layout",
|
|
708
|
+
],
|
|
709
|
+
notes: "Prefer adding screens directly to the current layout when the user wants to keep the same design language. Inspect the existing screens first, then copy the nearest matching screen structure and numeric styling values so the new screens align with the established composition.",
|
|
710
|
+
},
|
|
711
|
+
],
|
|
712
|
+
avoid: [
|
|
713
|
+
"Do not ask a generic 'what next' question after every successful operation.",
|
|
714
|
+
"Do not force menu-based interaction when the user already gave a concrete natural-language edit request.",
|
|
715
|
+
"Do not reduce the template catalog to a tiny recommendation list unless the user explicitly asks for that.",
|
|
716
|
+
"Do not describe templates without showing preview resources when template previews are available.",
|
|
717
|
+
"Do not read template previews one by one when browse_templates can show the full gallery in one step.",
|
|
718
|
+
"Do not begin a screenshot session with template browsing before the user has chosen create new app or edit existing project.",
|
|
719
|
+
"Do not use legacy generate_layouts or generate_graphics for the normal style chooser. Prepare a personalized catalog and apply the selected cached style instead.",
|
|
720
|
+
"Do not invent fresh x/y positions, widths, screenshot scale values, or headline styling for new screens when the user wants to preserve the current design language.",
|
|
721
|
+
"Do not report success for new screens or composition edits without checking that the new nodes match the surrounding screens and do not overlap key content.",
|
|
722
|
+
],
|
|
723
|
+
};
|