@getrheo/flow-runtime 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/agentPrompt/index.d.ts +72 -0
  2. package/dist/agentPrompt/index.js +739 -0
  3. package/dist/agentPrompt/index.js.map +1 -0
  4. package/dist/aiFlowGenerationMerge.d.ts +32 -0
  5. package/dist/aiFlowGenerationMerge.js +120 -0
  6. package/dist/aiFlowGenerationMerge.js.map +1 -0
  7. package/dist/animations.d.ts +110 -0
  8. package/dist/animations.js +312 -0
  9. package/dist/animations.js.map +1 -0
  10. package/dist/assignment.d.ts +7 -0
  11. package/dist/assignment.js +25 -0
  12. package/dist/assignment.js.map +1 -0
  13. package/dist/brandGradient.d.ts +57 -0
  14. package/dist/brandGradient.js +137 -0
  15. package/dist/brandGradient.js.map +1 -0
  16. package/dist/brandGradientManifestIssues.d.ts +11 -0
  17. package/dist/brandGradientManifestIssues.js +302 -0
  18. package/dist/brandGradientManifestIssues.js.map +1 -0
  19. package/dist/buildFlowPreview.d.ts +7 -0
  20. package/dist/buildFlowPreview.js +81 -0
  21. package/dist/buildFlowPreview.js.map +1 -0
  22. package/dist/buttonVariantChrome.d.ts +26 -0
  23. package/dist/buttonVariantChrome.js +59 -0
  24. package/dist/buttonVariantChrome.js.map +1 -0
  25. package/dist/checkboxGlyphStyle.d.ts +31 -0
  26. package/dist/checkboxGlyphStyle.js +241 -0
  27. package/dist/checkboxGlyphStyle.js.map +1 -0
  28. package/dist/choiceOptionSelection.d.ts +11 -0
  29. package/dist/choiceOptionSelection.js +120 -0
  30. package/dist/choiceOptionSelection.js.map +1 -0
  31. package/dist/colorAlpha.d.ts +8 -0
  32. package/dist/colorAlpha.js +48 -0
  33. package/dist/colorAlpha.js.map +1 -0
  34. package/dist/counterLayer.d.ts +42 -0
  35. package/dist/counterLayer.js +95 -0
  36. package/dist/counterLayer.js.map +1 -0
  37. package/dist/decisionEval.d.ts +27 -0
  38. package/dist/decisionEval.js +197 -0
  39. package/dist/decisionEval.js.map +1 -0
  40. package/dist/dropShadow.d.ts +26 -0
  41. package/dist/dropShadow.js +76 -0
  42. package/dist/dropShadow.js.map +1 -0
  43. package/dist/emailPasswordAuthValidation.d.ts +16 -0
  44. package/dist/emailPasswordAuthValidation.js +25 -0
  45. package/dist/emailPasswordAuthValidation.js.map +1 -0
  46. package/dist/flowBuilderRules.d.ts +15 -0
  47. package/dist/flowBuilderRules.js +368 -0
  48. package/dist/flowBuilderRules.js.map +1 -0
  49. package/dist/flowGraph.d.ts +19 -0
  50. package/dist/flowGraph.js +373 -0
  51. package/dist/flowGraph.js.map +1 -0
  52. package/dist/hyperlinkLabel.d.ts +19 -0
  53. package/dist/hyperlinkLabel.js +232 -0
  54. package/dist/hyperlinkLabel.js.map +1 -0
  55. package/dist/index.d.ts +48 -0
  56. package/dist/index.js +4200 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/interpolateTemplate.d.ts +44 -0
  59. package/dist/interpolateTemplate.js +188 -0
  60. package/dist/interpolateTemplate.js.map +1 -0
  61. package/dist/layerRotate.d.ts +10 -0
  62. package/dist/layerRotate.js +9 -0
  63. package/dist/layerRotate.js.map +1 -0
  64. package/dist/layerTypography.d.ts +36 -0
  65. package/dist/layerTypography.js +68 -0
  66. package/dist/layerTypography.js.map +1 -0
  67. package/dist/layers.d.ts +69 -0
  68. package/dist/layers.js +257 -0
  69. package/dist/layers.js.map +1 -0
  70. package/dist/layout/index.d.ts +57 -0
  71. package/dist/layout/index.js +151 -0
  72. package/dist/layout/index.js.map +1 -0
  73. package/dist/manifestBillingSlice.d.ts +17 -0
  74. package/dist/manifestBillingSlice.js +102 -0
  75. package/dist/manifestBillingSlice.js.map +1 -0
  76. package/dist/prepareAiGeneratedScreen.d.ts +17 -0
  77. package/dist/prepareAiGeneratedScreen.js +99 -0
  78. package/dist/prepareAiGeneratedScreen.js.map +1 -0
  79. package/dist/publish-exports.json +166 -0
  80. package/dist/responsive/breakpoints.d.ts +34 -0
  81. package/dist/responsive/breakpoints.js +52 -0
  82. package/dist/responsive/breakpoints.js.map +1 -0
  83. package/dist/responsive/index.d.ts +8 -0
  84. package/dist/responsive/index.js +307 -0
  85. package/dist/responsive/index.js.map +1 -0
  86. package/dist/responsive/layerResolve.d.ts +43 -0
  87. package/dist/responsive/layerResolve.js +168 -0
  88. package/dist/responsive/layerResolve.js.map +1 -0
  89. package/dist/responsive/merge.d.ts +19 -0
  90. package/dist/responsive/merge.js +74 -0
  91. package/dist/responsive/merge.js.map +1 -0
  92. package/dist/responsive/previewSafeAreaInsets.d.ts +14 -0
  93. package/dist/responsive/previewSafeAreaInsets.js +24 -0
  94. package/dist/responsive/previewSafeAreaInsets.js.map +1 -0
  95. package/dist/responsive/screenContainerResolve.d.ts +11 -0
  96. package/dist/responsive/screenContainerResolve.js +122 -0
  97. package/dist/responsive/screenContainerResolve.js.map +1 -0
  98. package/dist/responsive/screenShellInsets.d.ts +11 -0
  99. package/dist/responsive/screenShellInsets.js +26 -0
  100. package/dist/responsive/screenShellInsets.js.map +1 -0
  101. package/dist/restingMotion.d.ts +167 -0
  102. package/dist/restingMotion.js +484 -0
  103. package/dist/restingMotion.js.map +1 -0
  104. package/dist/rheoAgentManifestMerge.d.ts +33 -0
  105. package/dist/rheoAgentManifestMerge.js +55 -0
  106. package/dist/rheoAgentManifestMerge.js.map +1 -0
  107. package/dist/scaleInputStyle.d.ts +35 -0
  108. package/dist/scaleInputStyle.js +77 -0
  109. package/dist/scaleInputStyle.js.map +1 -0
  110. package/dist/scaleValidation.d.ts +9 -0
  111. package/dist/scaleValidation.js +21 -0
  112. package/dist/scaleValidation.js.map +1 -0
  113. package/dist/stateMachine.d.ts +105 -0
  114. package/dist/stateMachine.js +674 -0
  115. package/dist/stateMachine.js.map +1 -0
  116. package/dist/stepResponse-BXgoZ7o-.d.ts +112 -0
  117. package/dist/textInputValidation.d.ts +14 -0
  118. package/dist/textInputValidation.js +46 -0
  119. package/dist/textInputValidation.js.map +1 -0
  120. package/dist/translationPlaceholders.d.ts +9 -0
  121. package/dist/translationPlaceholders.js +52 -0
  122. package/dist/translationPlaceholders.js.map +1 -0
  123. package/dist/validation.d.ts +31 -0
  124. package/dist/validation.js +233 -0
  125. package/dist/validation.js.map +1 -0
  126. package/package.json +242 -0
@@ -0,0 +1,739 @@
1
+ import { ButtonActionSchema, MANIFEST_SCHEMA_VERSION, LAYER_KINDS, INPUT_LAYER_KINDS, migrateLegacyManifest, FlowManifestSchema, screenBackgroundPlaybackId, BUTTON_LAYER_VARIANTS, OS_PERMISSION_KEYS, TEXT_INPUT_TYPES, FIELD_CLASSIFICATIONS, OAUTH_LOGIN_PRESETS, EMAIL_PASSWORD_AUTH_MODES, ICON_FAMILIES, NORMALIZED_SURFACE_OUTCOMES, isScreenBackgroundPlaybackId } from '@getrheo/contracts';
2
+ import { minimalLayerExamples, LAYER_KINDS as LAYER_KINDS$1, INPUT_LAYER_KINDS as INPUT_LAYER_KINDS$1, isInputLayer, OS_PERMISSION_OUTCOME_END, OS_PERMISSION_OUTCOME_CONTINUE } from '@getrheo/contracts/layers';
3
+ import { minimalDecisionNodeExample, minimalExternalSurfaceExample } from '@getrheo/contracts/__fixtures__/agentGraphExamples';
4
+ import '@getrheo/contracts/localized';
5
+ import { collectCanvasGateViolations } from '@getrheo/contracts/canvasEditorGates';
6
+ import '@getrheo/contracts/decisions';
7
+
8
+ // src/agentPrompt/capabilitySurface.ts
9
+ var buttonActionKinds = () => ButtonActionSchema.options.map((option) => option.shape.kind.value);
10
+ var list = (values) => values.map((value) => `\`${value}\``).join(", ");
11
+ var bullets = (values) => values.map((value) => `- \`${value}\``).join("\n");
12
+ var buildCapabilitySurfaceFragment = () => [
13
+ `Manifest schema version: ${MANIFEST_SCHEMA_VERSION}`,
14
+ "",
15
+ "Valid layer kinds (only these; typos fail parse):",
16
+ LAYER_KINDS.join(", "),
17
+ "",
18
+ `Input layers (at most one per screen): ${INPUT_LAYER_KINDS.join(", ")}`,
19
+ "",
20
+ "Valid button action.kind values:",
21
+ bullets(buttonActionKinds()),
22
+ "",
23
+ "- go_to_step requires screenId.",
24
+ "- go_back_one_screen accepts optional fallbackScreenId.",
25
+ "- request_os_permission requires permissionKey and outcomes (granted/denied/blocked).",
26
+ "- play_media requires targetLayerIds (\u22651) pointing at Lottie/video layers on the same screen.",
27
+ "- request_app_review is allowed; requires screen.next.default. Do not emit unless the user asks.",
28
+ "- back_button takes no action (back navigation is built in).",
29
+ "",
30
+ `Button/back_button variants: ${list(BUTTON_LAYER_VARIANTS)}.`,
31
+ "",
32
+ "OS permission keys for request_os_permission:",
33
+ bullets(OS_PERMISSION_KEYS),
34
+ "",
35
+ `- text_input types: ${list(TEXT_INPUT_TYPES)}.`,
36
+ `- text_input classification: ${list(FIELD_CLASSIFICATIONS)}.`,
37
+ `- oauth_login preset providers: ${list(OAUTH_LOGIN_PRESETS)}.`,
38
+ `- email_password_auth modes: ${list(EMAIL_PASSWORD_AUTH_MODES)}.`,
39
+ `- icon families: ${list(ICON_FAMILIES)}.`,
40
+ "",
41
+ `External surface outcome keys: ${list(NORMALIZED_SURFACE_OUTCOMES)}. Every external surface needs fallback.`
42
+ ].join("\n");
43
+ var buildLayerExamplesFragment = () => {
44
+ const byKind = minimalLayerExamples();
45
+ const lines = LAYER_KINDS$1.map((kind) => {
46
+ const json = JSON.stringify(byKind[kind]);
47
+ return `- ${kind}: ${json}`;
48
+ });
49
+ return ["Minimal valid layer JSON per kind (from schema fixtures):", ...lines].join("\n");
50
+ };
51
+ var buildScreenStructuralRules = (screenLeadIn) => `Screen object (Zod ScreenSchema \u2014 invalid shapes are rejected):
52
+ ${screenLeadIn}
53
+ - id: /^scr_[a-z0-9_]+$/i (new id; never reuse ids from the anchor or manifest list you are given).
54
+ - name: 1\u201380 chars.
55
+ - regions: { body: StackLayer required; header?: StackLayer; footer?: StackLayer }.
56
+ - regions.body MUST be kind "stack" with direction ("vertical"|"horizontal"), gap (0\u2013200 int), children: Layer[].
57
+ - Stack layout: direction + gap live on the stack node. style on stacks is padding/margin/background/border/etc. only \u2014 not direction/gap.
58
+ - Child spacing (gap) defaults: body/content stacks gap 12; choice (single_choice/multiple_choice) and identity (oauth_login/email_password_auth) containers gap 8. Omit gap only when you want the standard spacing; otherwise set it explicitly.
59
+ - Explicit sizing: ALWAYS set style.width and style.height on every layer (no implicit/omitted sizing). Width values: "full" (fill parent), "auto" (hug content), a fraction ("1/2"|"1/3"|"2/3"|"1/4"|"3/4"), or a pixel number. Height values: "fill", "auto", or a pixel number (no fractions). Use these per-kind defaults unless the design needs otherwise:
60
+ - stack, text_input, scale_input, oauth_login, email_password_auth, email_password_field, progress, loader \u2192 width "full", height "fill"
61
+ - button, back_button, oauth_provider, email_password_submit, checkbox, single_choice, multiple_choice \u2192 width "full", height "auto"
62
+ - text, counter, icon, hyperlink \u2192 width "auto", height "auto"
63
+ - image, lottie, video \u2192 width "full", height a pixel number (e.g. 160)
64
+ - carousel \u2192 no style sizing on the outer layer
65
+ - next: { "default": null } for inserted screens (server rewires the default path).
66
+ - animations / stagger / transition / containerStyle: omit unless you are sure; invalid clip targets fail manifest validation. When setting shell chrome, use containerStyle.backgroundFill: { kind: "color"|"image"|"video", ... } (not legacy background).
67
+
68
+ Layer discriminant "kind" MUST be one of these strings exactly (case-sensitive; any other string fails parse):
69
+ ${LAYER_KINDS$1.join(", ")}
70
+
71
+ FORBIDDEN kind aliases (models often hallucinate these \u2014 they are NOT valid):
72
+ - NEVER "selection", "radio", "picker", "form", "input", "choices". For pick-one UI use kind "single_choice"; for pick-many use "multiple_choice".
73
+
74
+ Per-screen manifest rules (enforced after Zod \u2014 violations cause retry):
75
+ - **At most ONE input layer per screen**, counting only: ${INPUT_LAYER_KINDS$1.map((k) => `"${k}"`).join(", ")}. You cannot stack a text_input and a single_choice on the same screen, and you cannot output two choice layers. If the user asks for "every layer" or a "showcase", pick **one** input primitive OR none; use non-input layers (text, button, image, \u2026) for the rest.
76
+ - Do **not** combine identity capture with other inputs on the same screen: if you include "oauth_login" or "email_password_auth", omit single_choice, multiple_choice, text_input, and scale_input on that screen (product/editor rule).
77
+ - fieldKey on input layers and checkbox: /^[a-z][a-z0-9_]*$/ (snake_case, starts with letter). Each fieldKey must be unique in the merged flow when that layer captures data.
78
+
79
+ Representative shapes (read carefully \u2014 nested kinds must match):
80
+
81
+ - **text**: id, kind "text", text: LocalizedText { default: string, translations?: { \u2026 } }. style.fontWeight is a NUMBER (400, 700). Text align is style.align: "left"|"center"|"right" \u2014 never textAlign.
82
+
83
+ - **button**: kind "button", variant "primary"|"secondary"|"ghost"|"destructive", action (discriminated on action.kind): continue | skip | end_flow | go_back_one_screen (optional fallbackScreenId scr_*) | go_to_step { screenId } | request_os_permission { permissionKey, outcomes: { granted, denied, blocked } each a scr_* id, "continue", or "end" } | play_media { targetLayerIds } | request_app_review | none. children: Layer[] \u2014 label is always nested text layer(s), never a bare string on the button.
84
+
85
+ - **back_button**: kind "back_button", variant, children (same button chrome pattern as button; used for back affordance).
86
+
87
+ - **hyperlink**: kind "hyperlink", href (https: or mailto: URL string), children min 1 (link label lives in nested layers, usually text).
88
+
89
+ - **image** / **lottie** / **video**: kind, optional media ref; lottie and video optional loop, autoPlay, triggerLayerId, onComplete; video optional audioEnabled; button action play_media with targetLayerIds.
90
+
91
+ - **icon**: kind "icon", family "ionicons", iconName non-empty (Ionicons kebab-case, e.g. "star-outline").
92
+
93
+ - **progress**: kind "progress"; optional height (bar thickness in px, defaults to 6), trackColor, fillColor.
94
+
95
+ - **loader**: kind "loader"; optional variant "linear"|"circular", align "start"|"center"|"end", timing fields, onComplete discriminated { mode: "none"|"next"|"screen", screenId? }.
96
+
97
+ - **counter**: kind "counter", startValue and endValue required numbers; optional displayKind "number"|"time", timeFormat if time.
98
+
99
+ - **checkbox**: kind "checkbox", fieldKey, optional blocking and glyph styles.
100
+
101
+ - **single_choice** / **multiple_choice**: kind, fieldKey, children: **array of stack layers, minimum 2** (one stack per option). optionBindings: same length as children; each entry { optionId: string, rootLayerId: <that option stack's id> }. Option stacks may use style (default) and selectedStyle (selected). branching: { enabled: boolean, conditions: [{ choiceId, goTo }] }; use enabled:false and conditions:[] unless you emit real branch targets that exist in the flow (for a brand-new inserted screen, default false/empty is safest).
102
+
103
+ - **text_input**: kind, fieldKey, classification "safe"|"sensitive", optional placeholder / inputType ("plain"|"email"|"phone"|"url"|"multiline") / required / lengths, optional children.
104
+
105
+ - **scale_input**: kind, fieldKey, classification, numeric min and max (max > min), optional step, defaultValue, labels, optional children.
106
+
107
+ - **oauth_login**: kind, children: one or more **oauth_provider** layers only. Each oauth_provider is either variant "preset" with provider "google"|"github"|"apple" and optional label LocalizedText, OR variant "custom" with rowId (UUID string), buttonVariant, and children (icon/text like a button).
108
+
109
+ - **email_password_auth**: kind, mode "sign_in"|"sign_up", fieldKey, children mixing **email_password_field** (slot "email"|"password"|"confirm" \u2014 sign_up needs confirm, sign_in must not include confirm slot) and exactly one **email_password_submit** (buttonVariant + children with nested text).
110
+
111
+ - **carousel**: kind, slides: array of stack layers (min 1 slide).
112
+
113
+ When the user wants a dense demo: prefer a vertical body stack with decorative layers + **at most one** of: a choice layer, text_input, scale_input, oauth_login, or email_password_auth \u2014 not several of those capture types together.`;
114
+
115
+ // src/agentPrompt/screenSchemaRules.ts
116
+ var buildCanvasScreenSchemaRules = (opts) => {
117
+ const envelope = opts?.envelope ?? "screen";
118
+ const screenLeadIn = envelope === "flowGenStep" ? '- Each screen in { "done": false, "screen": <Screen> } follows ScreenSchema.' : 'JSON envelope (exactly one top-level key): { "screen": <Screen> }';
119
+ return [
120
+ buildScreenStructuralRules(screenLeadIn),
121
+ "",
122
+ buildLayerExamplesFragment(),
123
+ "",
124
+ buildCapabilitySurfaceFragment()
125
+ ].join("\n");
126
+ };
127
+ var buildFlowManifestPatchRules = () => {
128
+ const screenRules = buildScreenStructuralRules("Each screen object follows ScreenSchema:");
129
+ return `You edit a Rheo flow manifest via natural language. The server holds the authoritative draft and merges your output onto it.
130
+
131
+ Output format (strict):
132
+ 1. A short confirmation in plain language \u2014 written as if the changes are already live on the canvas. The user only sees this text AFTER the server applies your manifest patch, so use past tense ("I updated\u2026", "Added\u2026") and briefly summarize what changed.
133
+ 2. On its own line: ---MANIFEST---
134
+ 3. One JSON object \u2014 prefer the PATCH envelope below.
135
+
136
+ PATCH envelope (default \u2014 emit ONLY what changed):
137
+ { "mode": "patch", "patch": { "screens": [ /* only new or edited screens */ ] } }
138
+
139
+ Patch rules:
140
+ - Include in patch.screens ONLY the screens you create or modify; never re-emit unchanged screens.
141
+ - patch.removeScreenIds: array of scr_* ids to delete.
142
+ - patch.decisionNodes / patch.externalSurfaceNodes: include ONLY nodes you add or change (upserted by id). Omit to leave them unchanged.
143
+ - patch.entryScreenId: include only when the flow entry changes.
144
+ - patch.sdkAttributeKeys: include only when adding/removing keys (replaces the full list).
145
+ - Include a screen's next links only when that screen's wiring changed; otherwise leave the screen out of the patch.
146
+ - Never emit builderMeta, flowId, defaultLocale, locales, theme, or version \u2014 the server owns these.
147
+
148
+ FULL envelope (fallback \u2014 only when restructuring the entire flow, or after a patch fails validation twice):
149
+ { "manifest": <FlowManifest without builderMeta> }
150
+ - Include every screen, decision node, external surface, and graph wiring the draft needs after your edit.
151
+ - Keep manifest.flowId, defaultLocale, locales, theme, and version unchanged from the current draft.
152
+ - entryScreenId must reference an existing screen id when set.
153
+
154
+ Screen rules (each screen in patch.screens[] or manifest.screens[]):
155
+ ${screenRules}
156
+ - You MAY change screen.next links when the user asks to wire flows; otherwise preserve existing next wiring.
157
+ - New screens need unique scr_* ids; new layers need unique lyr_* ids across the entire manifest.`;
158
+ };
159
+ var buildDecisionSurfaceRules = () => {
160
+ const decision = JSON.stringify(minimalDecisionNodeExample(), null, 2);
161
+ const surface = JSON.stringify(minimalExternalSurfaceExample(), null, 2);
162
+ return `Decision nodes and external surfaces (patch.decisionNodes / patch.externalSurfaceNodes):
163
+
164
+ Decision node ids: dec_* (e.g. dec_platform). Cases branch on builtin (locale, platform), sdk.* keys, or field keys. Non-reserved sdk.* keys used in decisions must appear in sdkAttributeKeys.
165
+
166
+ Minimal decision node example:
167
+ ${decision}
168
+
169
+ External surface ids: surf_* (e.g. surf_paywall). config.provider is revenuecat or unspecified. outcomes map normalized keys to jump targets; fallback is required.
170
+
171
+ Minimal external surface example:
172
+ ${surface}`;
173
+ };
174
+ var buildRheoAgentSchemaRules = () => [
175
+ buildFlowManifestPatchRules(),
176
+ "",
177
+ buildDecisionSurfaceRules()
178
+ ].join("\n");
179
+ var walkLayers = (root, fn) => {
180
+ const visit = (l, depth) => {
181
+ fn(l, depth);
182
+ if (l.kind === "stack") l.children.forEach((c) => visit(c, depth + 1));
183
+ else if (l.kind === "carousel") l.slides.forEach((c) => visit(c, depth + 1));
184
+ else if (l.kind === "button") l.children.forEach((c) => visit(c, depth + 1));
185
+ else if (l.kind === "back_button") l.children.forEach((c) => visit(c, depth + 1));
186
+ else if (l.kind === "hyperlink") l.children.forEach((c) => visit(c, depth + 1));
187
+ else if (l.kind === "single_choice" || l.kind === "multiple_choice") {
188
+ l.children.forEach((c) => visit(c, depth + 1));
189
+ } else if (l.kind === "text_input" || l.kind === "scale_input") {
190
+ l.children?.forEach((c) => visit(c, depth + 1));
191
+ } else if (l.kind === "oauth_login") {
192
+ l.children.forEach((c) => visit(c, depth + 1));
193
+ } else if (l.kind === "oauth_provider" && l.variant === "custom") {
194
+ l.children.forEach((c) => visit(c, depth + 1));
195
+ } else if (l.kind === "email_password_auth") {
196
+ l.children.forEach((c) => visit(c, depth + 1));
197
+ } else if (l.kind === "email_password_field") {
198
+ l.children?.forEach((c) => visit(c, depth + 1));
199
+ } else if (l.kind === "email_password_submit") {
200
+ l.children.forEach((c) => visit(c, depth + 1));
201
+ }
202
+ };
203
+ visit(root, 0);
204
+ };
205
+ var walkScreen = (screen, fn) => {
206
+ if (screen.regions.header) walkLayers(screen.regions.header, fn);
207
+ walkLayers(screen.regions.body, fn);
208
+ if (screen.regions.footer) walkLayers(screen.regions.footer, fn);
209
+ };
210
+ var findLayerById = (screen, id) => {
211
+ let found = null;
212
+ walkScreen(screen, (l) => {
213
+ if (!found && l.id === id) found = l;
214
+ });
215
+ return found;
216
+ };
217
+
218
+ // src/flowBuilderRules.ts
219
+ var BUILDER_RULES_AGENT_BULLETS = [
220
+ "Connect flow entry on the canvas before publishing (entryScreenId must exist when screens are present).",
221
+ "Every text and icon layer needs explicit style.color (including nested button label text \u2014 native does not inherit colors).",
222
+ 'Screens with text_input, multiple_choice, or scale_input need a button with action.kind "continue".',
223
+ "At most one input layer per screen (single_choice, multiple_choice, text_input, scale_input).",
224
+ "Do not combine oauth_login or email_password_auth with other input layers on the same screen.",
225
+ "Only one oauth_login and one email_password_auth per screen; never both on the same screen.",
226
+ "fieldKey values must be unique snake_case across the flow.",
227
+ "Choice branch goTo and go_to_step screenId must reference existing screen ids.",
228
+ "request_app_review buttons require screen.next.default wired to a valid target.",
229
+ 'request_os_permission outcomes must target existing screens, "continue", or "end".',
230
+ 'Lottie/video with autoPlay false needs a button with action.kind "play_media" targeting that layer (or screen background video id).',
231
+ "play_media targetLayerIds must reference Lottie/video layers on the same screen or the screen background video playback id."
232
+ ];
233
+ var FIELD_KEY_RE = /^[a-z][a-z0-9_]*$/;
234
+ var styleBucketHasColor = (s) => s !== void 0 && s.color !== void 0;
235
+ var textLayerHasAuthoringColor = (l) => {
236
+ if (styleBucketHasColor(l.style)) return true;
237
+ const bp = l.styleBreakpoints;
238
+ if (!bp) return false;
239
+ return styleBucketHasColor(bp.sm) || styleBucketHasColor(bp.md) || styleBucketHasColor(bp.lg) || styleBucketHasColor(bp.xl) || styleBucketHasColor(bp["2xl"]);
240
+ };
241
+ var iconLayerHasAuthoringColor = (l) => {
242
+ if (styleBucketHasColor(l.style)) return true;
243
+ const bp = l.styleBreakpoints;
244
+ if (!bp) return false;
245
+ return styleBucketHasColor(bp.sm) || styleBucketHasColor(bp.md) || styleBucketHasColor(bp.lg) || styleBucketHasColor(bp.xl) || styleBucketHasColor(bp["2xl"]);
246
+ };
247
+ var collectFlowBuilderIssues = (manifest) => {
248
+ const issues = [];
249
+ const fieldKeyOwners = /* @__PURE__ */ new Map();
250
+ const screenIds = new Set(manifest.screens.map((s) => s.id));
251
+ const jumpTargetIds = /* @__PURE__ */ new Set([
252
+ ...screenIds,
253
+ ...manifest.decisionNodes.map((d) => d.id),
254
+ ...(manifest.externalSurfaceNodes ?? []).map((n) => n.id)
255
+ ]);
256
+ if (manifest.entryScreenId == null) {
257
+ if (manifest.screens.length > 0) {
258
+ issues.push(
259
+ "Connect the flow entry node on the canvas to where the flow starts (a screen, decision, or integration step)."
260
+ );
261
+ }
262
+ } else if (!jumpTargetIds.has(manifest.entryScreenId)) {
263
+ issues.push(`Flow entry target "${manifest.entryScreenId}" does not exist.`);
264
+ }
265
+ for (const screen of manifest.screens) {
266
+ let inputCount = 0;
267
+ let oauthLoginLayerCount = 0;
268
+ let emailPasswordAuthLayerCount = 0;
269
+ let needsManualSubmit = false;
270
+ let hasContinueButton = false;
271
+ const screenLabel = screen.name || screen.id;
272
+ const mediaLayerIds = /* @__PURE__ */ new Set();
273
+ const buttonLayerIds = /* @__PURE__ */ new Set();
274
+ const shellPlaybackId = screenBackgroundPlaybackId(screen.id);
275
+ const shellFill = screen.containerStyle?.backgroundFill;
276
+ const shellVideoFill = shellFill?.kind === "video" ? shellFill : void 0;
277
+ walkScreen(screen, (l) => {
278
+ if (l.kind === "button") buttonLayerIds.add(l.id);
279
+ });
280
+ if (shellFill?.kind === "image" || shellFill?.kind === "video") {
281
+ if (!shellFill.media?.mediaAssetId) {
282
+ issues.push(
283
+ `Screen "${screenLabel}" ${shellFill.kind} background needs a media asset.`
284
+ );
285
+ }
286
+ }
287
+ if (shellVideoFill) {
288
+ if (shellVideoFill.autoPlay === false) {
289
+ const triggerId = shellVideoFill.triggerLayerId?.trim();
290
+ if (!triggerId) {
291
+ issues.push(
292
+ `Screen "${screenLabel}" background video needs a trigger button when auto-play is off.`
293
+ );
294
+ } else if (!buttonLayerIds.has(triggerId)) {
295
+ issues.push(
296
+ `Screen "${screenLabel}" background video references a missing trigger button "${triggerId}".`
297
+ );
298
+ } else {
299
+ const btn = findLayerById(screen, triggerId);
300
+ if (!btn || btn.kind !== "button") {
301
+ issues.push(
302
+ `Screen "${screenLabel}" background video trigger must be a button layer.`
303
+ );
304
+ } else if (btn.action.kind !== "play_media") {
305
+ issues.push(
306
+ `Screen "${screenLabel}" background video trigger button must use On Tap \u2192 Play media.`
307
+ );
308
+ } else if (!btn.action.targetLayerIds.includes(shellPlaybackId)) {
309
+ issues.push(
310
+ `Screen "${screenLabel}" background video is not listed on trigger button "${triggerId}".`
311
+ );
312
+ }
313
+ }
314
+ } else if (shellVideoFill.triggerLayerId) {
315
+ const btn = findLayerById(screen, shellVideoFill.triggerLayerId);
316
+ if (btn?.kind === "button" && btn.action.kind === "play_media" && !btn.action.targetLayerIds.includes(shellPlaybackId)) {
317
+ issues.push(
318
+ `Screen "${screenLabel}" background video trigger button does not target the screen background.`
319
+ );
320
+ }
321
+ }
322
+ }
323
+ walkScreen(screen, (l) => {
324
+ if (l.kind === "lottie" || l.kind === "video") mediaLayerIds.add(l.id);
325
+ if (l.kind === "oauth_login") {
326
+ oauthLoginLayerCount += 1;
327
+ }
328
+ if (l.kind === "email_password_auth") {
329
+ emailPasswordAuthLayerCount += 1;
330
+ }
331
+ if (l.kind === "button" && l.action.kind === "continue") {
332
+ hasContinueButton = true;
333
+ }
334
+ if (isInputLayer(l)) {
335
+ inputCount += 1;
336
+ if (l.kind === "multiple_choice" || l.kind === "text_input" || l.kind === "scale_input") {
337
+ needsManualSubmit = true;
338
+ }
339
+ const key = l.fieldKey;
340
+ const label = screen.name || screen.id;
341
+ if (!key || key.length === 0) {
342
+ issues.push(`Screen "${label}" is missing a variable name (fieldKey).`);
343
+ } else if (!FIELD_KEY_RE.test(key)) {
344
+ issues.push(
345
+ `Screen "${label}" has an invalid variable name "${key}" \u2014 use snake_case (a\u2013z, 0\u20139, _).`
346
+ );
347
+ } else {
348
+ const owners = fieldKeyOwners.get(key) ?? [];
349
+ owners.push(label);
350
+ fieldKeyOwners.set(key, owners);
351
+ }
352
+ if (l.kind === "single_choice" || l.kind === "multiple_choice") {
353
+ for (const cond of l.branching.conditions) {
354
+ if (!screenIds.has(cond.goTo)) {
355
+ issues.push(
356
+ `Screen "${label}" branches choice "${cond.choiceId}" to a missing screen "${cond.goTo}".`
357
+ );
358
+ }
359
+ }
360
+ }
361
+ }
362
+ if (l.kind === "button" && l.action.kind === "go_to_step") {
363
+ if (!screenIds.has(l.action.screenId)) {
364
+ issues.push(
365
+ `Button "${l.name || l.id}" on screen "${screen.name || screen.id}" targets a missing screen "${l.action.screenId}".`
366
+ );
367
+ }
368
+ }
369
+ if (l.kind === "button" && l.action.kind === "request_app_review") {
370
+ const def = screen.next?.default;
371
+ if (def == null) {
372
+ issues.push(
373
+ `Button "${l.name || l.id}" on screen "${screen.name || screen.id}" requests app review but the screen has no default next step.`
374
+ );
375
+ } else if (!screenIds.has(def) && !manifest.decisionNodes?.some((d) => d.id === def)) {
376
+ issues.push(
377
+ `Button "${l.name || l.id}" on screen "${screen.name || screen.id}" requests app review but default next "${def}" is missing.`
378
+ );
379
+ }
380
+ }
381
+ if (l.kind === "button" && l.action.kind === "request_os_permission") {
382
+ const o = l.action.outcomes;
383
+ for (const slot of ["granted", "denied", "blocked"]) {
384
+ const sid = o[slot];
385
+ if (sid === OS_PERMISSION_OUTCOME_END) {
386
+ continue;
387
+ }
388
+ if (sid === OS_PERMISSION_OUTCOME_CONTINUE) {
389
+ const def = screen.next?.default;
390
+ if (def == null) {
391
+ continue;
392
+ }
393
+ if (!screenIds.has(def) && !manifest.decisionNodes?.some((d) => d.id === def)) {
394
+ issues.push(
395
+ `Button "${l.name || l.id}" on screen "${screen.name || screen.id}" (${slot}) continues to missing target "${def}".`
396
+ );
397
+ }
398
+ continue;
399
+ }
400
+ if (!screenIds.has(sid)) {
401
+ issues.push(
402
+ `Button "${l.name || l.id}" on screen "${screen.name || screen.id}" (${slot}) targets a missing screen "${sid}".`
403
+ );
404
+ }
405
+ }
406
+ }
407
+ if (l.kind === "back_button" && l.fallbackScreenId && !screenIds.has(l.fallbackScreenId)) {
408
+ issues.push(
409
+ `Back button "${l.name || l.id}" on screen "${screen.name || screen.id}" uses a missing fallback screen "${l.fallbackScreenId}".`
410
+ );
411
+ }
412
+ if (l.kind === "button" && l.action.kind === "go_back_one_screen" && l.action.fallbackScreenId) {
413
+ if (!screenIds.has(l.action.fallbackScreenId)) {
414
+ issues.push(
415
+ `Button "${l.name || l.id}" on screen "${screen.name || screen.id}" uses a missing fallback screen "${l.action.fallbackScreenId}".`
416
+ );
417
+ }
418
+ }
419
+ if (l.kind === "lottie" || l.kind === "video") {
420
+ const media = l;
421
+ if (media.autoPlay === false) {
422
+ const triggerId = media.triggerLayerId?.trim();
423
+ if (!triggerId) {
424
+ issues.push(
425
+ `${media.kind === "video" ? "Video" : "Lottie"} "${media.name || media.id}" on screen "${screenLabel}" needs a trigger button when auto-play is off.`
426
+ );
427
+ } else if (!buttonLayerIds.has(triggerId)) {
428
+ issues.push(
429
+ `${media.kind === "video" ? "Video" : "Lottie"} "${media.name || media.id}" on screen "${screenLabel}" references a missing trigger button "${triggerId}".`
430
+ );
431
+ } else {
432
+ const btn = findLayerById(screen, triggerId);
433
+ if (!btn || btn.kind !== "button") {
434
+ issues.push(
435
+ `${media.kind === "video" ? "Video" : "Lottie"} "${media.name || media.id}" on screen "${screenLabel}" trigger must be a button layer.`
436
+ );
437
+ } else if (btn.action.kind !== "play_media") {
438
+ issues.push(
439
+ `${media.kind === "video" ? "Video" : "Lottie"} "${media.name || media.id}" on screen "${screenLabel}" trigger button must use On Tap \u2192 Play media (or pick the trigger again from this screen).`
440
+ );
441
+ } else if (!btn.action.targetLayerIds.includes(media.id)) {
442
+ issues.push(
443
+ `${media.kind === "video" ? "Video" : "Lottie"} "${media.name || media.id}" on screen "${screenLabel}" is not listed on trigger button "${triggerId}".`
444
+ );
445
+ }
446
+ }
447
+ } else if (media.triggerLayerId) {
448
+ const btn = findLayerById(screen, media.triggerLayerId);
449
+ if (btn?.kind === "button" && btn.action.kind === "play_media" && !btn.action.targetLayerIds.includes(media.id)) {
450
+ issues.push(
451
+ `${media.kind === "video" ? "Video" : "Lottie"} "${media.name || media.id}" on screen "${screenLabel}" trigger button does not target this layer.`
452
+ );
453
+ }
454
+ }
455
+ }
456
+ if (l.kind === "button" && l.action.kind === "play_media") {
457
+ for (const targetId of l.action.targetLayerIds) {
458
+ if (targetId === shellPlaybackId) {
459
+ if (!shellVideoFill) {
460
+ issues.push(
461
+ `Button "${l.name || l.id}" on screen "${screenLabel}" targets screen background video, but this screen has no video background.`
462
+ );
463
+ }
464
+ continue;
465
+ }
466
+ if (isScreenBackgroundPlaybackId(targetId)) {
467
+ issues.push(
468
+ `Button "${l.name || l.id}" on screen "${screenLabel}" play-media target "${targetId}" is not valid for this screen.`
469
+ );
470
+ continue;
471
+ }
472
+ if (!mediaLayerIds.has(targetId)) {
473
+ issues.push(
474
+ `Button "${l.name || l.id}" on screen "${screenLabel}" play-media target "${targetId}" must be a Lottie or video layer on this screen, or screen background video.`
475
+ );
476
+ }
477
+ }
478
+ }
479
+ });
480
+ if (oauthLoginLayerCount > 0 && inputCount > 0) {
481
+ issues.push(
482
+ `Screen "${screen.name || screen.id}" cannot combine OAuth Login with input layers (${inputCount}). Split them onto separate screens.`
483
+ );
484
+ }
485
+ if (emailPasswordAuthLayerCount > 0 && inputCount > 0) {
486
+ issues.push(
487
+ `Screen "${screen.name || screen.id}" cannot combine Email / password login with input layers (${inputCount}). Split them onto separate screens.`
488
+ );
489
+ }
490
+ if (oauthLoginLayerCount > 0 && emailPasswordAuthLayerCount > 0) {
491
+ issues.push(
492
+ `Screen "${screen.name || screen.id}" cannot combine OAuth Login with Email / password login. Use one login block per screen.`
493
+ );
494
+ }
495
+ if (emailPasswordAuthLayerCount > 1) {
496
+ issues.push(
497
+ `Screen "${screen.name || screen.id}" has ${emailPasswordAuthLayerCount} Email / password login layers; only one is allowed per screen.`
498
+ );
499
+ }
500
+ if (oauthLoginLayerCount > 1) {
501
+ issues.push(
502
+ `Screen "${screen.name || screen.id}" has ${oauthLoginLayerCount} OAuth Login layers; only one is allowed per screen.`
503
+ );
504
+ }
505
+ if (inputCount > 1) {
506
+ issues.push(
507
+ `Screen "${screen.name || screen.id}" has ${inputCount} input layers; only one is allowed.`
508
+ );
509
+ }
510
+ if (needsManualSubmit && !hasContinueButton) {
511
+ issues.push(
512
+ `Screen "${screen.name || screen.id}" has a multiple_choice, text_input, or scale_input but no Button with action "continue". Add a Continue button so users can submit.`
513
+ );
514
+ }
515
+ }
516
+ for (const [key, owners] of fieldKeyOwners) {
517
+ if (owners.length > 1) {
518
+ issues.push(`Variable name "${key}" is used by multiple screens: ${owners.join(", ")}.`);
519
+ }
520
+ }
521
+ for (const screen of manifest.screens) {
522
+ walkScreen(screen, (l) => {
523
+ const screenLabel = screen.name || screen.id;
524
+ if (l.kind === "text" && !textLayerHasAuthoringColor(l)) {
525
+ issues.push(
526
+ `Screen "${screenLabel}": text layer "${l.id}" must set style.color for light and dark (CSS inheritance does not apply on native).`
527
+ );
528
+ }
529
+ if (l.kind === "icon" && !iconLayerHasAuthoringColor(l)) {
530
+ issues.push(
531
+ `Screen "${screenLabel}": icon layer "${l.id}" must set style.color for light and dark.`
532
+ );
533
+ }
534
+ });
535
+ }
536
+ return issues;
537
+ };
538
+
539
+ // src/agentPrompt/builderRulesFragment.ts
540
+ var buildBuilderRulesFragment = () => [
541
+ "Builder publish rules (enforced on merge \u2014 fix violations before retry):",
542
+ ...BUILDER_RULES_AGENT_BULLETS.map((b) => `- ${b}`)
543
+ ].join("\n");
544
+
545
+ // src/agentPrompt/layerStyling.ts
546
+ var buildLayerStylingRules = () => `Colors and custom styling (required when the user asks for specific colors, contrast, or different light vs dark appearance):
547
+ - Layers may include a "style" object. For fills and foregrounds use schema keys "background" and "color" only \u2014 never backgroundColor, borderColor, or CSS kebab-case.
548
+ - Values: hex strings (e.g. "#2563eb", "#ffffff") or per-appearance objects: { "light": "#...", "dark": "#..." } with at least one key. When the user wants different colors in light vs dark mode, prefer the object form for "background" and "color".
549
+ - Example solid blue CTA on a button layer: "variant":"primary","style":{"background":"#2563eb","color":"#ffffff"} (variant still required; style overrides the preset fill/label colors in the sim).
550
+ - Buttons: always set "variant" (primary|secondary|ghost|destructive). Preset variants are monochrome; when the user names a color (e.g. blue), add "style": { "background": "<hex or {light,dark}>", "color": "<readable contrast>" } on the button. You may instead set "color" on the nested text child inside button "children" for label tint.
551
+ - Text / stack / icon: "style".background, "style".color, "style".border, "style".radius work the same (ThemedColor for colored fields).
552
+ - Text alignment stays "align": left|center|right \u2014 never textAlign.`;
553
+
554
+ // src/agentPrompt/flowGenWorkflow.ts
555
+ var buildFlowGenWorkflowRules = () => `You are an expert mobile onboarding flow designer for Rheo.
556
+ Reply with ONE JSON object only (no markdown, no prose). Shape: { "done": boolean, "screen"?: <Screen> }
557
+ If done=true, omit "screen". If done=false, include a complete "screen".
558
+
559
+ Preferred pattern: one regions.body stack (vertical), padding only in body.style (CommonStyle), gap/direction on the stack itself.
560
+ Minimal valid screen (IDs are examples \u2014 use unique scr_/lyr_ ids):
561
+ {"id":"scr_intro","name":"Intro","regions":{"body":{"id":"lyr_body","kind":"stack","direction":"vertical","gap":16,"style":{"width":"full","height":"fill","padding":{"t":24,"r":20,"b":24,"l":20}},"children":[
562
+ {"id":"lyr_title","kind":"text","text":{"default":"Welcome"},"style":{"width":"auto","height":"auto","fontSize":24,"fontWeight":700,"align":"center","color":"#0f172a"}},
563
+ {"id":"lyr_sub","kind":"text","text":{"default":"Short subtitle."},"style":{"width":"auto","height":"auto","fontSize":16,"fontWeight":400,"align":"center","color":"#64748b"}},
564
+ {"id":"lyr_next","kind":"button","variant":"primary","action":{"kind":"continue"},"direction":"horizontal","align":"center","distribution":"center","style":{"width":"full","height":"auto"},"children":[
565
+ {"id":"lyr_next_t","kind":"text","text":{"default":"Continue"},"style":{"width":"auto","height":"auto","color":"#ffffff"}}
566
+ ]}
567
+ ]}},"next":{"default":null}}
568
+
569
+ Workflow rules:
570
+ - Mirror the user's language in default copy.
571
+ - Never change manifest.flowId, defaultLocale, locales, theme, version from the prompt payload.
572
+ - Screen/layer ids: /^scr_[a-z0-9_]+$/i and /^lyr_[a-z0-9_]+$/i only (letters, numbers, underscores).
573
+ - regions.body is required and must be kind "stack". Omit header/footer unless you need them.
574
+ - screen.next.default must be null \u2014 the server chains screens on the default path.
575
+ - Avoid carousel and branching choices unless necessary \u2014 they are easier to get wrong.
576
+
577
+ Placeholder scr_blank still present \u2192 done=false with first real screen replacing it.
578
+ User flow complete on default path \u2192 done=true.
579
+
580
+ Never done=true until a real screen exists (not before replacing scr_blank).`;
581
+
582
+ // src/agentPrompt/manifestContext.ts
583
+ var themeSummary = (manifest) => {
584
+ const t = manifest.theme;
585
+ if (!t) return "theme: (default)";
586
+ const parts = [];
587
+ if (t.primary) parts.push(`primary=${t.primary}`);
588
+ if (t.background) parts.push(`background=${t.background}`);
589
+ if (t.foreground) parts.push(`foreground=${t.foreground}`);
590
+ if (t.fontFamily) parts.push(`fontFamily=${t.fontFamily}`);
591
+ return parts.length > 0 ? `theme: ${parts.join(", ")}` : "theme: (partial/default)";
592
+ };
593
+ var graphSummary = (manifest) => {
594
+ const screenLines = manifest.screens.map((s) => `- ${s.id}: ${s.name}${s.next.default ? ` \u2192 ${s.next.default}` : " \u2192 (end)"}`).join("\n");
595
+ const decisionIds = (manifest.decisionNodes ?? []).map((n) => n.id);
596
+ const surfaceIds = (manifest.externalSurfaceNodes ?? []).map((n) => n.id);
597
+ const sdkKeys = manifest.sdkAttributeKeys ?? [];
598
+ return [
599
+ `schemaVersion: ${manifest.schemaVersion ?? "(unset)"}`,
600
+ `defaultLocale: ${manifest.defaultLocale}`,
601
+ themeSummary(manifest),
602
+ `sdkAttributeKeys: ${sdkKeys.length ? sdkKeys.join(", ") : "(none)"}`,
603
+ `entryScreenId: ${manifest.entryScreenId ?? "(unset)"}`,
604
+ `screens:
605
+ ${screenLines || "(none)"}`,
606
+ `decisionNodes: ${decisionIds.length ? decisionIds.join(", ") : "(none)"}`,
607
+ `externalSurfaceNodes: ${surfaceIds.length ? surfaceIds.join(", ") : "(none)"}`
608
+ ].join("\n");
609
+ };
610
+ var focusedDecisionIds = (selection) => {
611
+ if (selection.decisionIds?.length) return selection.decisionIds;
612
+ if (selection.decisionId) return [selection.decisionId];
613
+ return [];
614
+ };
615
+ var focusedScreenIds = (selection) => {
616
+ if (selection.screenIds?.length) return selection.screenIds;
617
+ if (selection.screenId) return [selection.screenId];
618
+ return [];
619
+ };
620
+ var screensJson = (manifest, ids) => manifest.screens.filter((s) => ids.includes(s.id));
621
+ var slimManifestForRheoAgentPrompt = (manifest, selection) => {
622
+ const summary = graphSummary(manifest);
623
+ if (!selection) {
624
+ return `Flow graph summary (server holds the full draft \u2014 emit patch entries only for what you change):
625
+ ${summary}`;
626
+ }
627
+ const screenIds = focusedScreenIds(selection);
628
+ if (screenIds.length > 0) {
629
+ const focused = screensJson(manifest, screenIds);
630
+ return `Focused screens (full JSON for the canvas selection):
631
+ ${JSON.stringify(focused)}
632
+
633
+ Rest of the flow (graph summary \u2014 emit patch entries only for screens/nodes you change):
634
+ ${summary}`;
635
+ }
636
+ const decisionIds = focusedDecisionIds(selection);
637
+ if (decisionIds.length > 0) {
638
+ const focused = (manifest.decisionNodes ?? []).filter((n) => decisionIds.includes(n.id));
639
+ return `Focused decision nodes (full JSON):
640
+ ${JSON.stringify(focused)}
641
+
642
+ Rest of the flow (graph summary):
643
+ ${summary}`;
644
+ }
645
+ if (selection.externalSurfaceId) {
646
+ const node = (manifest.externalSurfaceNodes ?? []).find(
647
+ (n) => n.id === selection.externalSurfaceId
648
+ );
649
+ return `Focused external surface node (full JSON):
650
+ ${JSON.stringify(node ?? null)}
651
+
652
+ Rest of the flow (graph summary):
653
+ ${summary}`;
654
+ }
655
+ return `Flow graph summary (server holds the full draft \u2014 emit patch entries only for what you change):
656
+ ${summary}`;
657
+ };
658
+ var buildRheoAgentSystemPrompt = (draftManifest, selection, parts) => {
659
+ const gatesBlock = parts.canvasGatesLine ? `
660
+ App-specific canvas editor policy (must comply):
661
+ ${parts.canvasGatesLine}
662
+ ` : "";
663
+ return `You are Rheo Agent \u2014 an in-builder assistant that edits mobile onboarding flow manifests.
664
+
665
+ ${parts.schemaRules}
666
+
667
+ ${parts.builderRules}
668
+
669
+ ${parts.layerStyling}
670
+ ${gatesBlock}
671
+ Current draft (builderMeta omitted \u2014 do not invent builderMeta):
672
+ ${slimManifestForRheoAgentPrompt(draftManifest, selection)}
673
+
674
+ ${parts.selectionBlock}
675
+
676
+ When the user attaches screenshot(s), treat them as visual reference for layout, copy, colors, and styling.`;
677
+ };
678
+ var validateManifest = (data) => {
679
+ const migrated = migrateLegacyManifest(data);
680
+ const result = FlowManifestSchema.safeParse(migrated);
681
+ if (result.success) return { ok: true, manifest: result.data };
682
+ return {
683
+ ok: false,
684
+ issues: result.error.issues.map((i) => {
685
+ const screenIdx = i.path[1];
686
+ const screenId = typeof screenIdx === "number" && Array.isArray(data?.screens) ? data.screens[screenIdx]?.id : void 0;
687
+ return {
688
+ stepId: screenId ?? null,
689
+ path: [...i.path],
690
+ message: i.message,
691
+ code: i.code
692
+ };
693
+ })
694
+ };
695
+ };
696
+
697
+ // src/agentPrompt/validate.ts
698
+ var validateAgentManifestOutput = (data, opts) => {
699
+ const validated = validateManifest(data);
700
+ if (!validated.ok) {
701
+ return { ok: false, issues: validated.issues };
702
+ }
703
+ const issues = [];
704
+ if (opts?.includeBuilderRules !== false) {
705
+ for (const message of collectFlowBuilderIssues(validated.manifest)) {
706
+ issues.push({
707
+ path: [],
708
+ message,
709
+ code: "builder.rule",
710
+ stepId: null
711
+ });
712
+ }
713
+ }
714
+ if (opts?.canvasGates) {
715
+ for (const message of collectCanvasGateViolations(validated.manifest, opts.canvasGates)) {
716
+ issues.push({
717
+ path: [],
718
+ message,
719
+ code: "canvas.gate",
720
+ stepId: null
721
+ });
722
+ }
723
+ }
724
+ if (issues.length > 0) {
725
+ return { ok: false, issues };
726
+ }
727
+ return { ok: true, manifest: validated.manifest };
728
+ };
729
+ var formatAgentManifestIssues = (issues, max = 18) => JSON.stringify(
730
+ issues.slice(0, max).map((i) => ({
731
+ path: i.path.join("."),
732
+ message: i.message,
733
+ code: i.code
734
+ }))
735
+ );
736
+
737
+ export { BUILDER_RULES_AGENT_BULLETS, buildBuilderRulesFragment, buildCanvasScreenSchemaRules, buildCapabilitySurfaceFragment, buildDecisionSurfaceRules, buildFlowGenWorkflowRules, buildFlowManifestPatchRules, buildLayerExamplesFragment, buildLayerStylingRules, buildRheoAgentSchemaRules, buildRheoAgentSystemPrompt, buttonActionKinds, formatAgentManifestIssues, slimManifestForRheoAgentPrompt, validateAgentManifestOutput };
738
+ //# sourceMappingURL=index.js.map
739
+ //# sourceMappingURL=index.js.map