@agent-native/core 0.90.1 → 0.90.2

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 (295) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +14 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/AgentPanel.tsx +1 -1
  5. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +11 -6
  6. package/corpus/core/src/client/use-action.ts +39 -2
  7. package/corpus/core/src/client/use-db-sync.ts +44 -18
  8. package/corpus/core/src/db/create-get-db.ts +32 -2
  9. package/corpus/core/src/server/framework-request-handler.ts +52 -4
  10. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +5 -0
  11. package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +34 -6
  12. package/corpus/templates/assets/AGENTS.md +23 -4
  13. package/corpus/templates/assets/actions/_generation-preset-settings.ts +48 -0
  14. package/corpus/templates/assets/actions/_helpers.ts +8 -1
  15. package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +64 -0
  16. package/corpus/templates/assets/actions/create-generation-preset.ts +11 -3
  17. package/corpus/templates/assets/actions/generate-image-batch.ts +7 -2
  18. package/corpus/templates/assets/actions/generate-image.ts +342 -25
  19. package/corpus/templates/assets/actions/get-library.ts +4 -3
  20. package/corpus/templates/assets/actions/navigate.ts +2 -1
  21. package/corpus/templates/assets/actions/refresh-generation-run.ts +5 -1
  22. package/corpus/templates/assets/actions/update-asset.ts +1 -0
  23. package/corpus/templates/assets/actions/update-generation-preset.ts +8 -1
  24. package/corpus/templates/assets/actions/view-screen.ts +8 -1
  25. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +9 -1
  26. package/corpus/templates/assets/app/hooks/use-image-model-menu.ts +1 -0
  27. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +11 -0
  28. package/corpus/templates/assets/app/i18n/zh-TW.ts +64 -0
  29. package/corpus/templates/assets/app/i18n-data.ts +650 -0
  30. package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -1
  31. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +44 -33
  32. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1474 -0
  33. package/corpus/templates/assets/app/routes/library.tsx +43 -1
  34. package/corpus/templates/assets/server/lib/generation.ts +366 -44
  35. package/corpus/templates/assets/server/lib/image-processing.ts +252 -0
  36. package/corpus/templates/assets/server/lib/preset-skeleton.ts +137 -0
  37. package/corpus/templates/assets/shared/api.ts +47 -0
  38. package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -2
  39. package/corpus/templates/clips/app/components/library/sort-menu.tsx +1 -1
  40. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +114 -33
  41. package/corpus/templates/clips/app/routes/_app.library._index.tsx +27 -1
  42. package/corpus/templates/clips/app/routes/record.tsx +157 -68
  43. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +5 -5
  44. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +20 -3
  45. package/corpus/templates/clips/shared/finalize-recovery.ts +23 -3
  46. package/corpus/templates/content/AGENTS.md +52 -52
  47. package/corpus/templates/content/actions/_builder-cms-read-client.ts +188 -8
  48. package/corpus/templates/content/actions/_builder-docs-client.ts +41 -3
  49. package/corpus/templates/content/actions/_database-source-utils.ts +998 -160
  50. package/corpus/templates/content/actions/_database-utils.ts +44 -4
  51. package/corpus/templates/content/actions/_notion-action-utils.ts +6 -2
  52. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -1
  53. package/corpus/templates/content/actions/attach-content-database-source.ts +85 -12
  54. package/corpus/templates/content/actions/change-content-database-source-role.ts +9 -1
  55. package/corpus/templates/content/actions/delete-content-database.ts +1 -0
  56. package/corpus/templates/content/actions/get-document.ts +11 -2
  57. package/corpus/templates/content/actions/list-content-databases.ts +57 -9
  58. package/corpus/templates/content/actions/prepare-builder-source-review.ts +23 -6
  59. package/corpus/templates/content/actions/refresh-content-database-source.ts +26 -3
  60. package/corpus/templates/content/actions/restore-content-database.ts +1 -0
  61. package/corpus/templates/content/actions/update-document.ts +43 -0
  62. package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +21 -0
  63. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +0 -1
  64. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +70 -28
  65. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +69 -17
  66. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +4 -4
  67. package/corpus/templates/content/app/components/editor/body-hydration.ts +112 -0
  68. package/corpus/templates/content/app/components/editor/builder-body-hydration-pump.ts +52 -0
  69. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +9076 -2876
  70. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +41 -1
  71. package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +36 -11
  72. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +21 -1
  73. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +11 -1
  74. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +11 -8
  75. package/corpus/templates/content/app/global.css +41 -0
  76. package/corpus/templates/content/app/hooks/use-content-database.ts +346 -42
  77. package/corpus/templates/content/app/hooks/use-db-sync.ts +9 -0
  78. package/corpus/templates/content/app/hooks/use-documents.ts +47 -0
  79. package/corpus/templates/content/app/hooks/use-notion.ts +12 -10
  80. package/corpus/templates/content/app/i18n/zh-TW.ts +35 -0
  81. package/corpus/templates/content/app/i18n-data.ts +355 -0
  82. package/corpus/templates/content/app/root.tsx +22 -0
  83. package/corpus/templates/content/changelog/2026-07-01-builder-database-sources-keep-loading-rows-in-the-background.md +6 -0
  84. package/corpus/templates/content/changelog/2026-07-01-builder-source-attach-starts-with-usable-rows.md +6 -0
  85. package/corpus/templates/content/changelog/2026-07-01-builder-source-attachment-now-stays-connected-after-large.md +6 -0
  86. package/corpus/templates/content/changelog/2026-07-01-builder-source-refreshes-now-show-progress-for-large-docs-blog.md +6 -0
  87. package/corpus/templates/content/changelog/2026-07-01-database-pages-now-delete-to-trash-without-route-errors.md +6 -0
  88. package/corpus/templates/content/changelog/2026-07-02-builder-source-refreshes-recover-from-stalled-background-row.md +6 -0
  89. package/corpus/templates/content/changelog/2026-07-02-builder-sourced-rows-open-with-sync-protection.md +6 -0
  90. package/corpus/templates/content/changelog/2026-07-02-database-rows-and-pages-open-faster-from-recently-loaded-dat.md +6 -0
  91. package/corpus/templates/content/changelog/2026-07-02-preview-panels-close-reliably-narrow-sidebar-row-actions-sta.md +6 -0
  92. package/corpus/templates/content/changelog/2026-07-06-builder-sync-progress-no-longer-shows-continue.md +6 -0
  93. package/corpus/templates/content/shared/api.ts +13 -0
  94. package/corpus/templates/content/shared/builder-mdx.ts +16 -3
  95. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +23 -13
  96. package/corpus/templates/design/.agents/skills/responsive-breakpoints/SKILL.md +98 -0
  97. package/corpus/templates/design/.agents/skills/shader-fills/SKILL.md +162 -0
  98. package/corpus/templates/design/AGENTS.md +9 -2
  99. package/corpus/templates/design/actions/add-breakpoint.ts +6 -3
  100. package/corpus/templates/design/actions/apply-a11y-fix.ts +57 -37
  101. package/corpus/templates/design/actions/apply-component-prop-edit.ts +41 -20
  102. package/corpus/templates/design/actions/apply-motion-edit.ts +251 -38
  103. package/corpus/templates/design/actions/apply-shader-fill.ts +90 -66
  104. package/corpus/templates/design/actions/apply-visual-edit.ts +282 -55
  105. package/corpus/templates/design/actions/create-component.ts +29 -60
  106. package/corpus/templates/design/actions/create-file.ts +9 -2
  107. package/corpus/templates/design/actions/duplicate-design.ts +31 -2
  108. package/corpus/templates/design/actions/edit-design.ts +36 -38
  109. package/corpus/templates/design/actions/generate-design-title.ts +87 -0
  110. package/corpus/templates/design/actions/generate-design.ts +53 -18
  111. package/corpus/templates/design/actions/generate-screens.ts +108 -4
  112. package/corpus/templates/design/actions/get-motion-timeline.ts +35 -7
  113. package/corpus/templates/design/actions/insert-asset.ts +34 -29
  114. package/corpus/templates/design/actions/insert-design-native-asset.ts +129 -32
  115. package/corpus/templates/design/actions/insert-figma-library-asset.ts +35 -30
  116. package/corpus/templates/design/actions/present-design-variants.ts +7 -2
  117. package/corpus/templates/design/actions/remove-motion-timeline.ts +31 -36
  118. package/corpus/templates/design/actions/update-file.ts +190 -75
  119. package/corpus/templates/design/app/components/design/BreakpointBar.tsx +403 -0
  120. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +469 -222
  121. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +708 -53
  122. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +83 -3
  123. package/corpus/templates/design/app/components/design/EditPanel.tsx +1091 -6948
  124. package/corpus/templates/design/app/components/design/LayersPanel.tsx +419 -85
  125. package/corpus/templates/design/app/components/design/MotionDock.tsx +1124 -103
  126. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2610 -1398
  127. package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +22 -0
  128. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2182 -235
  129. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +187 -11
  130. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +376 -1
  131. package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +746 -0
  132. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +17 -0
  133. package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +1 -1
  134. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +1 -3
  135. package/corpus/templates/design/app/components/design/code-workbench/quickinput/QuickInput.tsx +1 -1
  136. package/corpus/templates/design/app/components/design/edit-panel/appearance-properties.tsx +518 -0
  137. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +562 -0
  138. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +763 -0
  139. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +94 -0
  140. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +780 -0
  141. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +446 -0
  142. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +79 -0
  143. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +315 -0
  144. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +344 -0
  145. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +594 -0
  146. package/corpus/templates/design/app/components/design/edit-panel/frame-presets-panel.tsx +119 -0
  147. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +248 -0
  148. package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +45 -0
  149. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +693 -0
  150. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +724 -0
  151. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +207 -0
  152. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +628 -0
  153. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +91 -0
  154. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +649 -0
  155. package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +118 -0
  156. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +88 -0
  157. package/corpus/templates/design/app/components/design/edit-panel/transform-helpers.ts +100 -0
  158. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +117 -0
  159. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +501 -0
  160. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +91 -40
  161. package/corpus/templates/design/app/components/design/inspector/BreakpointOverrideIndicator.tsx +82 -0
  162. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +189 -87
  163. package/corpus/templates/design/app/components/design/inspector/GlslShaderPanel.tsx +1097 -0
  164. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +44 -0
  165. package/corpus/templates/design/app/components/design/inspector/InteractionStatePanel.tsx +258 -0
  166. package/corpus/templates/design/app/components/design/inspector/MotionKeyframeDiamond.tsx +144 -0
  167. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +99 -9
  168. package/corpus/templates/design/app/components/design/inspector/frame-size-presets.ts +116 -0
  169. package/corpus/templates/design/app/components/design/inspector/index.ts +26 -0
  170. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +29 -0
  171. package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +171 -0
  172. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +220 -0
  173. package/corpus/templates/design/app/components/design/multi-screen/chrome-transitions.ts +35 -0
  174. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +134 -0
  175. package/corpus/templates/design/app/components/design/multi-screen/culling.ts +185 -0
  176. package/corpus/templates/design/app/components/design/multi-screen/gradient-overlay-geometry.ts +120 -0
  177. package/corpus/templates/design/app/components/design/multi-screen/types.ts +892 -0
  178. package/corpus/templates/design/app/components/design/multi-screen/vector-edit-geometry.ts +21 -0
  179. package/corpus/templates/design/app/components/design/types.ts +15 -0
  180. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +109 -5
  181. package/corpus/templates/design/app/global.css +8 -8
  182. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +259 -2
  183. package/corpus/templates/design/app/i18n/zh-TW.ts +163 -9
  184. package/corpus/templates/design/app/i18n-breakpoints.ts +205 -0
  185. package/corpus/templates/design/app/i18n-data.ts +1664 -152
  186. package/corpus/templates/design/app/i18n-motion.ts +563 -0
  187. package/corpus/templates/design/app/lib/design-import.ts +73 -0
  188. package/corpus/templates/design/app/pages/Index.tsx +41 -22
  189. package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +1 -1
  190. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +1 -1
  191. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +1 -1
  192. package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +1 -1
  193. package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +1 -1
  194. package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +1 -1
  195. package/corpus/templates/design/changelog/2026-07-05-designs-get-ai-generated-names-from-your-prompt.md +6 -0
  196. package/corpus/templates/design/changelog/2026-07-05-local-code-screens-keep-their-connection-when-duplicated-and.md +6 -0
  197. package/corpus/templates/design/changelog/2026-07-05-press-a-to-draw-screens-on-the-canvas-inside-a-screen-it-dr.md +6 -0
  198. package/corpus/templates/design/changelog/2026-07-06-3d-rotation-and-perspective-controls-on-every-element.md +6 -0
  199. package/corpus/templates/design/changelog/2026-07-06-ai-generated-and-duplicated-screens-now-get-their-editor-sel.md +6 -0
  200. package/corpus/templates/design/changelog/2026-07-06-applying-shaders-and-fills-in-quick-succession-can-no-longer.md +6 -0
  201. package/corpus/templates/design/changelog/2026-07-06-breakpoint-frames-in-the-overview-now-support-click-to-selec.md +6 -0
  202. package/corpus/templates/design/changelog/2026-07-06-breakpoint-segments-in-the-inspector-now-show-a-device-icon-.md +6 -0
  203. package/corpus/templates/design/changelog/2026-07-06-breakpoint-targeting-now-lives-in-one-compact-control-in-the.md +6 -0
  204. package/corpus/templates/design/changelog/2026-07-06-canvas-edits-can-no-longer-be-overwritten-by-stale-collabora.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-06-canvas-edits-inside-screens-save-reliably-again.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-06-cleaner-inspector-removed-the-ask-ai-box-from-the-design-tab.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-06-clicking-empty-canvas-space-now-deselects-an-element-inside-.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-06-dragging-an-element-onto-a-rectangle-now-nests-it-inside-wit.md +6 -0
  209. package/corpus/templates/design/changelog/2026-07-06-dragging-screens-and-objects-on-the-canvas-is-now-much-smoot.md +6 -0
  210. package/corpus/templates/design/changelog/2026-07-06-drawing-a-shape-or-text-on-the-canvas-no-longer-flashes-away.md +6 -0
  211. package/corpus/templates/design/changelog/2026-07-06-dropping-an-element-between-two-others-inside-a-screen-now-s.md +6 -0
  212. package/corpus/templates/design/changelog/2026-07-06-duplicating-a-screen-now-places-the-copy-exactly-where-you-d.md +6 -0
  213. package/corpus/templates/design/changelog/2026-07-06-edit-gradients-directly-on-elements-inside-screens-scale-str.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-06-edits-from-collaborators-and-the-agent-now-update-the-canvas.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-06-fixed-a-bug-where-most-style-position-and-text-edits-briefly.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-06-fixed-a-confusing-raw-error-message-when-a-file-upload-fails.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-06-fixed-a-rare-case-where-an-edit-could-be-silently-overwritte.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-06-fixed-inserted-assets-sometimes-disappearing-reappearing-or-.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-06-fixed-inspector-value-scrubbing-producing-erratic-numbers-an.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-06-fixed-padding-gap-and-min-max-size-scrubbing-not-updating-th.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-06-fixed-text-stroke-color-turning-black-and-glyphs-disappearin.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-06-fixed-the-collapsed-effects-and-stroke-inspector-sections-sh.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-06-fixed-the-new-design-dialog-closing-unexpectedly-when-using-.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-06-fixed-the-typography-section-missing-when-selecting-text-nes.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-06-full-view-no-longer-flashes-back-to-the-overview-on-entry-no.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-06-keyframe-animation-timeline-per-property-keyframes-easing-cu.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-06-moving-restyling-and-scrubbing-values-on-elements-in-ai-gene.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-06-padding-and-corner-radius-value-scrubbing-now-snaps-to-whole.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-06-padding-handles-now-only-resize-on-the-handle-line-itself-sh.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-06-preview-and-edit-hover-focus-and-pressed-states-with-real-cs.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-06-promoting-an-element-to-a-component-now-works-correctly-for-.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-06-reduced-url-history-churn-and-selection-overlay-flicker-whil.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-06-removed-the-decorative-dot-before-screen-names-in-the-overvi.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-06-responsive-breakpoints-edit-at-any-width-with-cascading-over.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-06-screen-labels-no-longer-turn-blue-when-you-hover-inside-the-.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-06-scrolling-a-screen-with-an-element-selected-stays-smooth-on-.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-06-selecting-multiple-elements-and-dragging-now-moves-the-whole.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-06-selection-borders-handles-and-value-readouts-now-stay-the-sa.md +6 -0
  239. package/corpus/templates/design/changelog/2026-07-06-selection-handles-and-editing-chrome-now-stay-a-constant-siz.md +6 -0
  240. package/corpus/templates/design/changelog/2026-07-06-shader-fills-and-effects-ai-authored-or-preset-glsl-shaders-.md +6 -0
  241. package/corpus/templates/design/changelog/2026-07-06-text-drawn-on-the-canvas-now-defaults-to-white-and-inter-in-.md +6 -0
  242. package/corpus/templates/design/changelog/2026-07-06-text-dropped-into-a-shape-now-stays-readable-instead-of-disa.md +6 -0
  243. package/corpus/templates/design/changelog/2026-07-06-the-add-tweak-controls-prompt-no-longer-jumps-to-the-corner-.md +6 -0
  244. package/corpus/templates/design/changelog/2026-07-06-the-padding-value-readout-now-stays-visible-while-you-hover-.md +6 -0
  245. package/corpus/templates/design/changelog/2026-07-06-the-selected-element-attachment-now-clears-from-the-chat-com.md +6 -0
  246. package/corpus/templates/design/changelog/2026-07-06-the-zoom-menu-is-more-compact-with-tight-rows-and-right-alig.md +6 -0
  247. package/corpus/templates/design/changelog/2026-07-06-undo-and-redo-no-longer-flash-the-screen-changes-now-apply-i.md +6 -0
  248. package/corpus/templates/design/changelog/2026-07-06-zoom-presets-like-zoom-to-50-now-stay-on-the-focused-screen-.md +6 -0
  249. package/corpus/templates/design/server/lib/import-design-files.ts +11 -2
  250. package/corpus/templates/design/server/source-workspace.ts +139 -59
  251. package/corpus/templates/design/shared/board-file.ts +11 -2
  252. package/corpus/templates/design/shared/breakpoint-media.ts +543 -0
  253. package/corpus/templates/design/shared/canvas-math.ts +354 -35
  254. package/corpus/templates/design/shared/code-layer.ts +277 -8
  255. package/corpus/templates/design/shared/design-state.ts +11 -4
  256. package/corpus/templates/design/shared/interaction-states.ts +548 -0
  257. package/corpus/templates/design/shared/motion-compiler.ts +186 -23
  258. package/corpus/templates/design/shared/motion-easing.ts +294 -0
  259. package/corpus/templates/design/shared/motion-timeline.ts +400 -34
  260. package/corpus/templates/design/shared/prompt-title.ts +95 -0
  261. package/corpus/templates/design/shared/responsive-classes.ts +448 -10
  262. package/corpus/templates/design/shared/screen-annotation.ts +46 -0
  263. package/corpus/templates/design/shared/shader-fills.ts +1009 -0
  264. package/corpus/templates/design/shared/shader-presets.ts +852 -4
  265. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +15 -2
  266. package/corpus/templates/slides/app/components/editor/ImportButton.tsx +10 -1
  267. package/corpus/templates/slides/app/lib/upload-response.ts +71 -0
  268. package/dist/client/AgentPanel.js +1 -1
  269. package/dist/client/AgentPanel.js.map +1 -1
  270. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  271. package/dist/client/rich-markdown-editor/useCollabReconcile.js +11 -6
  272. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  273. package/dist/client/use-action.d.ts +3 -0
  274. package/dist/client/use-action.d.ts.map +1 -1
  275. package/dist/client/use-action.js +34 -3
  276. package/dist/client/use-action.js.map +1 -1
  277. package/dist/client/use-db-sync.d.ts +4 -0
  278. package/dist/client/use-db-sync.d.ts.map +1 -1
  279. package/dist/client/use-db-sync.js +37 -18
  280. package/dist/client/use-db-sync.js.map +1 -1
  281. package/dist/collab/routes.d.ts +1 -1
  282. package/dist/collab/struct-routes.d.ts +1 -1
  283. package/dist/db/create-get-db.d.ts +25 -0
  284. package/dist/db/create-get-db.d.ts.map +1 -1
  285. package/dist/db/create-get-db.js +25 -2
  286. package/dist/db/create-get-db.js.map +1 -1
  287. package/dist/observability/routes.d.ts +3 -3
  288. package/dist/secrets/routes.d.ts +9 -9
  289. package/dist/server/framework-request-handler.d.ts.map +1 -1
  290. package/dist/server/framework-request-handler.js +37 -1
  291. package/dist/server/framework-request-handler.js.map +1 -1
  292. package/package.json +1 -1
  293. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +0 -153
  294. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +0 -133
  295. package/corpus/templates/design/app/pages/DesignEditor.tsx +0 -22924
@@ -89,9 +89,11 @@ import { cn } from "@/lib/utils";
89
89
 
90
90
  import type {
91
91
  AssetVariantState,
92
+ ImageModel,
92
93
  ImageQualityTier,
93
94
  StyleStrength,
94
95
  } from "../../shared/api";
96
+ import { MODEL_ASPECT_RATIOS } from "../../shared/api";
95
97
  import {
96
98
  DEFAULT_LIBRARY_PRESETS,
97
99
  LibraryPreset,
@@ -2053,6 +2055,37 @@ export function AssetPickerSurface() {
2053
2055
  const [visibleCandidateRunIds, setVisibleCandidateRunIds] = useState<
2054
2056
  string[]
2055
2057
  >(() => hostConfig.candidateRunIds ?? []);
2058
+ // The picker generates with the composer's default image model
2059
+ // (`imageGenerationModel`); it does not pick a model itself. Read that default
2060
+ // so the aspect-ratio choices can be constrained for models that only support
2061
+ // a subset (e.g. gpt-image-2 → 1:1 / 2:3 / 3:2). Read-once is enough here: the
2062
+ // embedded picker has no image-model control of its own.
2063
+ const [imageModelDefault, setImageModelDefault] = useState<ImageModel | null>(
2064
+ null,
2065
+ );
2066
+ useEffect(() => {
2067
+ let cancelled = false;
2068
+ void readClientAppState<{ model?: string }>("imageGenerationModel")
2069
+ .then((state) => {
2070
+ if (!cancelled && state?.model) {
2071
+ setImageModelDefault(state.model as ImageModel);
2072
+ }
2073
+ })
2074
+ .catch(() => {});
2075
+ return () => {
2076
+ cancelled = true;
2077
+ };
2078
+ }, []);
2079
+ // Only override the picker's curated ratio list when the selected image model
2080
+ // actually restricts ratios; otherwise keep the full curated set. Video mode
2081
+ // is unaffected by the image model.
2082
+ const ratioOptions = useMemo<readonly string[]>(() => {
2083
+ if (mediaType !== "image") return ASPECT_RATIOS;
2084
+ return (
2085
+ (imageModelDefault && MODEL_ASPECT_RATIOS[imageModelDefault]) ??
2086
+ ASPECT_RATIOS
2087
+ );
2088
+ }, [imageModelDefault, mediaType]);
2056
2089
 
2057
2090
  useEffect(() => {
2058
2091
  setHostConfig((current) => ({ ...current, ...urlHostConfig }));
@@ -2073,6 +2106,15 @@ export function AssetPickerSurface() {
2073
2106
  setAssetTab(urlAssetTab ?? "all");
2074
2107
  }, [urlAssetTab]);
2075
2108
 
2109
+ useEffect(() => {
2110
+ // If the current ratio isn't valid for the selected model (e.g. a 16:9
2111
+ // default while gpt-image-2 is active), snap to the first supported ratio so
2112
+ // the picker can't submit an unsupported pairing.
2113
+ if (!ratioOptions.includes(aspectRatio)) {
2114
+ setAspectRatio(ratioOptions[0]);
2115
+ }
2116
+ }, [ratioOptions, aspectRatio]);
2117
+
2076
2118
  const handleAssetTabChange = useCallback(
2077
2119
  (value: AssetTab) => {
2078
2120
  setAssetTab(value);
@@ -2781,7 +2823,7 @@ export function AssetPickerSurface() {
2781
2823
  </SelectTrigger>
2782
2824
  <SelectContent>
2783
2825
  <SelectGroup>
2784
- {ASPECT_RATIOS.map((ratio) => (
2826
+ {ratioOptions.map((ratio) => (
2785
2827
  <SelectItem key={ratio} value={ratio}>
2786
2828
  {ratio}
2787
2829
  </SelectItem>
@@ -1,3 +1,5 @@
1
+ import { createHash } from "node:crypto";
2
+
1
3
  import {
2
4
  FeatureNotConfiguredError,
3
5
  getBuilderImageGenerationBaseUrl,
@@ -5,6 +7,7 @@ import {
5
7
  resolveSecret,
6
8
  } from "@agent-native/core/server";
7
9
  import { and, eq, inArray } from "drizzle-orm";
10
+ import sharp from "sharp";
8
11
 
9
12
  import type {
10
13
  AspectRatio,
@@ -41,8 +44,10 @@ export interface GenerateProviderInput {
41
44
  compiledPrompt: string;
42
45
  references: ReferenceForGeneration[];
43
46
  model: ImageModel;
47
+ mode?: "generate" | "edit";
44
48
  aspectRatio: AspectRatio;
45
49
  imageSize: ImageSize;
50
+ background?: "transparent" | "opaque" | "auto";
46
51
  groundingMode: "auto" | "off" | "google-search";
47
52
  intent?: GenerationIntent;
48
53
  styleStrength?: StyleStrength;
@@ -77,6 +82,42 @@ const MANAGED_PROVIDER_INFLIGHT_MAX_POLLS =
77
82
  process.env.NODE_ENV === "test" ? 6 : 40;
78
83
  const MANAGED_PROVIDER_INFLIGHT_POLL_MS =
79
84
  process.env.NODE_ENV === "test" ? 0 : 6000;
85
+ // Slow image models (e.g. gpt-image-*) return the finished image synchronously
86
+ // but can take several minutes end-to-end (generate + encode the large PNG +
87
+ // transfer). The old 90s window was tuned for Gemini and aborted these mid-
88
+ // response, so the request just needs a longer budget rather than the poll-and-
89
+ // replay fallback. Override per deployment with ASSETS_IMAGE_GENERATION_TIMEOUT_MS.
90
+ const IMAGE_GENERATION_REQUEST_TIMEOUT_MS =
91
+ Number(process.env.ASSETS_IMAGE_GENERATION_TIMEOUT_MS) || 300_000;
92
+
93
+ // Lightweight structured logging for the image-generation providers. Slow
94
+ // models (e.g. gpt-image-*) can exceed the 90s per-request abort window and get
95
+ // converted into an in-flight poll, so these logs make the provider, model,
96
+ // elapsed time, and abort/poll outcome visible when a generation "completes on
97
+ // the provider but times out in the app". Silenced under NODE_ENV=test.
98
+ function logGeneration(
99
+ event: string,
100
+ fields: Record<string, unknown> = {},
101
+ ): void {
102
+ if (process.env.NODE_ENV === "test") return;
103
+ const parts = Object.entries(fields)
104
+ .filter(([, value]) => value !== undefined)
105
+ .map(([key, value]) => `${key}=${formatLogValue(value)}`)
106
+ .join(" ");
107
+ console.info(`[assets] image-gen ${event}${parts ? ` ${parts}` : ""}`);
108
+ }
109
+
110
+ function formatLogValue(value: unknown): string {
111
+ if (typeof value === "string") return value;
112
+ if (
113
+ typeof value === "number" ||
114
+ typeof value === "boolean" ||
115
+ value === null
116
+ ) {
117
+ return String(value);
118
+ }
119
+ return JSON.stringify(value);
120
+ }
80
121
 
81
122
  export async function getGeminiApiKey(): Promise<string> {
82
123
  const key = await resolveSecret("GEMINI_API_KEY");
@@ -232,6 +273,59 @@ export async function generateWithBuilderImageApi(
232
273
  }
233
274
 
234
275
  const baseUrl = getBuilderImageGenerationBaseUrl().replace(/\/$/, "");
276
+ const requestModel = toBuilderImageModel(input.model);
277
+ const startedAt = Date.now();
278
+ const requestBody = {
279
+ idempotencyKey: input.runId,
280
+ prompt: input.compiledPrompt,
281
+ model: requestModel,
282
+ ...(input.mode ? { mode: input.mode } : {}),
283
+ count: 1,
284
+ aspectRatio: toBuilderAspectRatio(input.aspectRatio),
285
+ size: toBuilderImageSize(input.imageSize),
286
+ outputFormat: "png",
287
+ ...(input.background ? { background: input.background } : {}),
288
+ references: input.references.map((ref) => ({
289
+ id: ref.id,
290
+ role: toBuilderReferenceRole(ref.role),
291
+ mimeType: ref.mimeType,
292
+ data: ref.data,
293
+ name: ref.category,
294
+ })),
295
+ source: {
296
+ appId: "assets",
297
+ feature: "generate-image",
298
+ resourceId: input.libraryId,
299
+ },
300
+ metadata: {
301
+ collectionId: input.collectionId ?? undefined,
302
+ callerAppId: input.callerAppId,
303
+ source: input.source,
304
+ groundingMode: input.groundingMode,
305
+ intent: input.intent,
306
+ },
307
+ };
308
+ logGeneration("builder.request", {
309
+ model: requestModel,
310
+ mode: input.mode,
311
+ requestedModel: input.model,
312
+ host: safeUrlHost(baseUrl),
313
+ aspectRatio: toBuilderAspectRatio(input.aspectRatio),
314
+ size: toBuilderImageSize(input.imageSize),
315
+ references: input.references.length,
316
+ referenceRoles: input.references
317
+ .map((ref) => `${ref.id}:${toBuilderReferenceRole(ref.role)}`)
318
+ .join(","),
319
+ runId: input.runId,
320
+ });
321
+ if (
322
+ input.mode === "edit" ||
323
+ process.env.ASSETS_IMAGE_GENERATION_DEBUG_PAYLOAD
324
+ ) {
325
+ logGeneration("builder.request_payload", {
326
+ payload: await sanitizedBuilderRequestPayload(requestBody),
327
+ });
328
+ }
235
329
  const response = await fetch(`${baseUrl}/generations`, {
236
330
  method: "POST",
237
331
  headers: {
@@ -239,40 +333,24 @@ export async function generateWithBuilderImageApi(
239
333
  "x-builder-api-key": builderCredentials.publicKey,
240
334
  "Content-Type": "application/json",
241
335
  },
242
- body: JSON.stringify({
243
- idempotencyKey: input.runId,
244
- prompt: input.compiledPrompt,
245
- model: toBuilderImageModel(input.model),
246
- count: 1,
247
- aspectRatio: toBuilderAspectRatio(input.aspectRatio),
248
- size: toBuilderImageSize(input.imageSize),
249
- outputFormat: "png",
250
- references: input.references.map((ref) => ({
251
- id: ref.id,
252
- role: toBuilderReferenceRole(ref.role),
253
- mimeType: ref.mimeType,
254
- data: ref.data,
255
- name: ref.category,
256
- })),
257
- source: {
258
- appId: "assets",
259
- feature: "generate-image",
260
- resourceId: input.libraryId,
261
- },
262
- metadata: {
263
- collectionId: input.collectionId ?? undefined,
264
- callerAppId: input.callerAppId,
265
- source: input.source,
266
- groundingMode: input.groundingMode,
267
- intent: input.intent,
268
- },
269
- }),
270
- signal: AbortSignal.timeout(90_000),
336
+ body: JSON.stringify(requestBody),
337
+ signal: AbortSignal.timeout(IMAGE_GENERATION_REQUEST_TIMEOUT_MS),
271
338
  }).catch((err) => {
272
- if ((err as Error)?.name === "AbortError") {
273
- // The socket aborted, but the service keeps generating under this
274
- // idempotency key. Flag it so the retry loop polls the key instead of
275
- // starting a fresh (double-charged) generation.
339
+ // `AbortSignal.timeout()` rejects with a `TimeoutError` DOMException, while a
340
+ // manual `AbortController.abort()` rejects with `AbortError` treat both as
341
+ // "socket gave up, but the service keeps generating under this idempotency
342
+ // key". Flag it so the retry loop polls the key instead of starting a fresh
343
+ // (double-charged) generation. Matching only "AbortError" silently let the
344
+ // per-request timeout escape as a hard failure for slow models.
345
+ const name = (err as Error)?.name;
346
+ if (name === "AbortError" || name === "TimeoutError") {
347
+ logGeneration("builder.abort", {
348
+ model: requestModel,
349
+ elapsedMs: Date.now() - startedAt,
350
+ timeoutMs: IMAGE_GENERATION_REQUEST_TIMEOUT_MS,
351
+ runId: input.runId,
352
+ note: "socket aborted at client timeout; provider may still be running under this idempotency key",
353
+ });
276
354
  throw new BuilderImageGenerationError(
277
355
  "Builder-managed image generation timed out.",
278
356
  504,
@@ -280,9 +358,23 @@ export async function generateWithBuilderImageApi(
280
358
  "client_timeout",
281
359
  );
282
360
  }
361
+ logGeneration("builder.fetch_error", {
362
+ model: requestModel,
363
+ elapsedMs: Date.now() - startedAt,
364
+ name: (err as Error)?.name,
365
+ runId: input.runId,
366
+ });
283
367
  throw err;
284
368
  });
285
369
 
370
+ logGeneration("builder.response", {
371
+ model: requestModel,
372
+ status: response.status,
373
+ ok: response.ok,
374
+ elapsedMs: Date.now() - startedAt,
375
+ runId: input.runId,
376
+ });
377
+
286
378
  if (!response.ok) {
287
379
  const text = await response.text().catch(() => "");
288
380
  const detail = extractBuilderErrorDetail(text);
@@ -315,6 +407,14 @@ export async function generateWithBuilderImageApi(
315
407
  );
316
408
  }
317
409
 
410
+ logGeneration("builder.success", {
411
+ requestedModel: input.model,
412
+ providerModel: body.model.publicId || input.model,
413
+ provider: body.model.provider,
414
+ elapsedMs: Date.now() - startedAt,
415
+ runId: input.runId,
416
+ });
417
+
318
418
  return {
319
419
  image: Buffer.from(await imageResponse.arrayBuffer()),
320
420
  mimeType:
@@ -329,6 +429,125 @@ export async function generateWithBuilderImageApi(
329
429
  };
330
430
  }
331
431
 
432
+ function safeUrlHost(url: string): string {
433
+ try {
434
+ return new URL(url).host;
435
+ } catch {
436
+ return "invalid-url";
437
+ }
438
+ }
439
+
440
+ async function sanitizedBuilderRequestPayload(input: {
441
+ idempotencyKey?: string;
442
+ prompt: string;
443
+ model: string;
444
+ mode?: string;
445
+ count: number;
446
+ aspectRatio: string;
447
+ size: string;
448
+ outputFormat: string;
449
+ background?: string;
450
+ references: Array<{
451
+ id: string;
452
+ role: string;
453
+ mimeType: string;
454
+ data: string;
455
+ name?: string;
456
+ }>;
457
+ source: Record<string, unknown>;
458
+ metadata: Record<string, unknown>;
459
+ }): Promise<Record<string, unknown>> {
460
+ return {
461
+ ...input,
462
+ promptChars: input.prompt.length,
463
+ references: await Promise.all(
464
+ input.references.map(async (reference) => ({
465
+ id: reference.id,
466
+ role: reference.role,
467
+ mimeType: reference.mimeType,
468
+ name: reference.name,
469
+ data: await referenceDataDebugSummary(reference.data),
470
+ })),
471
+ ),
472
+ };
473
+ }
474
+
475
+ async function referenceDataDebugSummary(data: string): Promise<
476
+ | {
477
+ omitted: true;
478
+ bytes: number;
479
+ sha256: string;
480
+ width?: number;
481
+ height?: number;
482
+ channels?: number;
483
+ hasAlpha?: boolean;
484
+ alpha?: {
485
+ min: number;
486
+ max: number;
487
+ transparent: number;
488
+ opaque: number;
489
+ partial: number;
490
+ };
491
+ }
492
+ | { omitted: true; error: string; chars: number }
493
+ > {
494
+ try {
495
+ const buffer = decodeReferenceData(data);
496
+ const metadata = await sharp(buffer, { failOn: "none" }).metadata();
497
+ const alpha = metadata.hasAlpha
498
+ ? await alphaDebugSummary(buffer).catch(() => undefined)
499
+ : undefined;
500
+ return {
501
+ omitted: true,
502
+ bytes: buffer.byteLength,
503
+ sha256: createHash("sha256").update(buffer).digest("hex").slice(0, 16),
504
+ width: metadata.width,
505
+ height: metadata.height,
506
+ channels: metadata.channels,
507
+ hasAlpha: metadata.hasAlpha,
508
+ alpha,
509
+ };
510
+ } catch (err) {
511
+ return {
512
+ omitted: true,
513
+ error: err instanceof Error ? err.message : "Unable to inspect reference",
514
+ chars: data.length,
515
+ };
516
+ }
517
+ }
518
+
519
+ function decodeReferenceData(data: string): Buffer {
520
+ const base64 = data.includes(",") ? data.split(",").pop()! : data;
521
+ return Buffer.from(base64, "base64");
522
+ }
523
+
524
+ async function alphaDebugSummary(data: Buffer): Promise<{
525
+ min: number;
526
+ max: number;
527
+ transparent: number;
528
+ opaque: number;
529
+ partial: number;
530
+ }> {
531
+ const alpha = await sharp(data, { failOn: "none" })
532
+ .ensureAlpha()
533
+ .extractChannel("alpha")
534
+ .raw()
535
+ .toBuffer();
536
+ let min = 255;
537
+ let max = 0;
538
+ let transparent = 0;
539
+ let opaque = 0;
540
+ let partial = 0;
541
+ for (const value of alpha) {
542
+ if (value < min) min = value;
543
+ if (value > max) max = value;
544
+ if (value === 0) transparent += 1;
545
+ else if (value === 255) opaque += 1;
546
+ else partial += 1;
547
+ }
548
+ return { min, max, transparent, opaque, partial };
549
+ }
550
+
332
551
  async function generateWithRetryingBuilderImageApi(
333
552
  input: GenerateProviderInput,
334
553
  ): Promise<GenerateProviderOutput> {
@@ -344,6 +563,13 @@ async function generateWithRetryingBuilderImageApi(
344
563
  // until it resolves or we exhaust the polling budget.
345
564
  if (isInFlightImageGenerationError(err)) {
346
565
  if (inFlightPolls >= MANAGED_PROVIDER_INFLIGHT_MAX_POLLS) {
566
+ logGeneration("builder.poll_exhausted", {
567
+ model: input.model,
568
+ polls: inFlightPolls,
569
+ maxPolls: MANAGED_PROVIDER_INFLIGHT_MAX_POLLS,
570
+ runId: input.runId,
571
+ note: "provider never returned a completed result under this idempotency key within the wait budget",
572
+ });
347
573
  throw new BuilderImageGenerationError(
348
574
  "Builder-managed image generation is still running after the wait budget. It may finish shortly — try again to pick up the result.",
349
575
  504,
@@ -352,6 +578,16 @@ async function generateWithRetryingBuilderImageApi(
352
578
  );
353
579
  }
354
580
  inFlightPolls += 1;
581
+ logGeneration("builder.poll", {
582
+ model: input.model,
583
+ poll: inFlightPolls,
584
+ maxPolls: MANAGED_PROVIDER_INFLIGHT_MAX_POLLS,
585
+ code:
586
+ err instanceof BuilderImageGenerationError ? err.code : undefined,
587
+ status:
588
+ err instanceof BuilderImageGenerationError ? err.status : undefined,
589
+ runId: input.runId,
590
+ });
355
591
  await generationPollDelay();
356
592
  continue;
357
593
  }
@@ -361,6 +597,13 @@ async function generateWithRetryingBuilderImageApi(
361
597
  transientAttempts < MANAGED_PROVIDER_MAX_ATTEMPTS - 1
362
598
  ) {
363
599
  transientAttempts += 1;
600
+ logGeneration("builder.transient_retry", {
601
+ model: input.model,
602
+ attempt: transientAttempts,
603
+ status:
604
+ err instanceof BuilderImageGenerationError ? err.status : undefined,
605
+ runId: input.runId,
606
+ });
364
607
  await generationRetryDelay(transientAttempts);
365
608
  continue;
366
609
  }
@@ -372,6 +615,14 @@ async function generateWithRetryingBuilderImageApi(
372
615
  export async function generateWithManagedImageProvider(
373
616
  input: GenerateProviderInput,
374
617
  ): Promise<GenerateProviderOutput> {
618
+ logGeneration("dispatch", {
619
+ model: input.model,
620
+ mode: input.mode,
621
+ intent: input.intent,
622
+ background: input.background,
623
+ builderEnabled: isBuilderImageGenerationEnabled(),
624
+ runId: input.runId,
625
+ });
375
626
  if (!isBuilderImageGenerationEnabled()) {
376
627
  if (await isManualImageGenerationConfigured()) {
377
628
  return generateWithManualImageProvider(input);
@@ -392,6 +643,12 @@ export async function generateWithManagedImageProvider(
392
643
  err instanceof BuilderImageGenerationError &&
393
644
  [401, 402, 403, 429, 503, 504].includes(err.status ?? 0);
394
645
  if (shouldFallback && (await isManualImageGenerationConfigured())) {
646
+ logGeneration("builder.fallback_to_manual", {
647
+ model: input.model,
648
+ status:
649
+ err instanceof BuilderImageGenerationError ? err.status : undefined,
650
+ runId: input.runId,
651
+ });
395
652
  return generateWithManualImageProvider(input);
396
653
  }
397
654
  if (shouldFallback && err instanceof BuilderImageGenerationError) {
@@ -648,6 +905,15 @@ export function sanitizeStyleBrief(value: Record<string, unknown>): StyleBrief {
648
905
  async function generateWithManualImageProvider(
649
906
  input: GenerateProviderInput,
650
907
  ): Promise<GenerateProviderOutput> {
908
+ if (input.mode === "edit") {
909
+ throw new FeatureNotConfiguredError({
910
+ requiredCredential: "BUILDER_PRIVATE_KEY",
911
+ builderConnectUrl: "/_agent-native/builder/connect",
912
+ byokDocsUrl: "https://aistudio.google.com/apikey",
913
+ message:
914
+ "Mask inpainting runs need Builder-managed image generation because the manual fallback cannot pass the image-edit mask.",
915
+ });
916
+ }
651
917
  if (await isGeminiImageGenerationConfigured()) {
652
918
  return generateWithGemini(input);
653
919
  }
@@ -666,6 +932,14 @@ async function generateWithManualImageProvider(
666
932
  export async function generateWithOpenAI(
667
933
  input: GenerateProviderInput,
668
934
  ): Promise<GenerateProviderOutput> {
935
+ const startedAt = Date.now();
936
+ const model = openAIImageModelForInput(input.model);
937
+ logGeneration("openai.request", {
938
+ model,
939
+ requestedModel: input.model,
940
+ size: toOpenAIImageSize(input.aspectRatio),
941
+ runId: input.runId,
942
+ });
669
943
  const response = await fetch("https://api.openai.com/v1/images/generations", {
670
944
  method: "POST",
671
945
  headers: {
@@ -673,14 +947,30 @@ export async function generateWithOpenAI(
673
947
  "Content-Type": "application/json",
674
948
  },
675
949
  body: JSON.stringify({
676
- model: "gpt-image-2",
950
+ model,
677
951
  prompt: buildOpenAIImagePrompt(input),
678
952
  n: 1,
679
953
  size: toOpenAIImageSize(input.aspectRatio),
680
954
  quality: "medium",
681
955
  output_format: "png",
956
+ ...(input.background ? { background: input.background } : {}),
682
957
  }),
683
- signal: AbortSignal.timeout(90_000),
958
+ signal: AbortSignal.timeout(IMAGE_GENERATION_REQUEST_TIMEOUT_MS),
959
+ }).catch((err) => {
960
+ logGeneration("openai.fetch_error", {
961
+ elapsedMs: Date.now() - startedAt,
962
+ name: (err as Error)?.name,
963
+ timeoutMs: IMAGE_GENERATION_REQUEST_TIMEOUT_MS,
964
+ runId: input.runId,
965
+ });
966
+ throw err;
967
+ });
968
+
969
+ logGeneration("openai.response", {
970
+ status: response.status,
971
+ ok: response.ok,
972
+ elapsedMs: Date.now() - startedAt,
973
+ runId: input.runId,
684
974
  });
685
975
 
686
976
  const body = (await response.json().catch(() => ({}))) as {
@@ -697,7 +987,7 @@ export async function generateWithOpenAI(
697
987
  return {
698
988
  image: Buffer.from(output.b64_json, "base64"),
699
989
  mimeType: "image/png",
700
- model: "gpt-image-2",
990
+ model,
701
991
  provider: "openai",
702
992
  };
703
993
  }
@@ -714,7 +1004,7 @@ export async function generateWithOpenAI(
714
1004
  image: Buffer.from(await imageResponse.arrayBuffer()),
715
1005
  mimeType:
716
1006
  imageResponse.headers.get("content-type")?.split(";")[0] || "image/png",
717
- model: "gpt-image-2",
1007
+ model,
718
1008
  provider: "openai",
719
1009
  sourceUrl: output.url,
720
1010
  };
@@ -722,6 +1012,14 @@ export async function generateWithOpenAI(
722
1012
  throw new Error("OpenAI returned no image data.");
723
1013
  }
724
1014
 
1015
+ function openAIImageModelForInput(
1016
+ model: ImageModel,
1017
+ ): "gpt-image-1" | "gpt-image-2" {
1018
+ return model === "gpt-image-1" || model === "gpt-image-2"
1019
+ ? model
1020
+ : "gpt-image-2";
1021
+ }
1022
+
725
1023
  function buildOpenAIImagePrompt(input: GenerateProviderInput): string {
726
1024
  if (!input.references.length) return input.compiledPrompt;
727
1025
  return `${input.compiledPrompt}
@@ -856,8 +1154,11 @@ function toBuilderReferenceRole(role: string) {
856
1154
  return "logo";
857
1155
  case "product_reference":
858
1156
  return "product";
1157
+ case "background_reference":
859
1158
  case "diagram_reference":
860
1159
  return "composition";
1160
+ case "mask":
1161
+ return "mask";
861
1162
  case "subject_reference":
862
1163
  case "edit_target":
863
1164
  return "source";
@@ -877,6 +1178,9 @@ export function compilePrompt(input: {
877
1178
  textPlacement?: string | null;
878
1179
  referenceCount: number;
879
1180
  includeLogo: boolean;
1181
+ skeletonContentMode?: "cutout" | "fill" | null;
1182
+ hasBackgroundPlate?: boolean;
1183
+ skeletonInpaint?: boolean;
880
1184
  aspectRatio?: AspectRatio;
881
1185
  imageSize?: ImageSize;
882
1186
  category?: ImageCategory;
@@ -899,6 +1203,16 @@ export function compilePrompt(input: {
899
1203
  const logoInstruction = input.includeLogo
900
1204
  ? "\nLeave a clean uncluttered area in the upper-right for the real brand logo; do not draw or approximate the logo yourself."
901
1205
  : "";
1206
+ const cutoutInstruction =
1207
+ input.skeletonContentMode === "cutout" && !input.skeletonInpaint
1208
+ ? "\nCutout mode: generate the subject in isolation on an empty transparent background; no scenery, no baked-in ground plane, no environment, and no baked-in shadow."
1209
+ : "";
1210
+ const backgroundPlateInstruction = input.hasBackgroundPlate
1211
+ ? "\nA background plate is attached as a composition reference. It is the FIXED brand layout your output will be composited onto — it already contains the logo, framing, and all fixed text/headlines. Render ONLY the subject, isolated on a transparent background. Do NOT draw, repeat, restyle, or overlap the plate's logo, headline, body text, CTA, or framing. Keep the subject within the plate's open/empty area and leave the occupied regions clear."
1212
+ : "";
1213
+ const skeletonInpaintInstruction = input.skeletonInpaint
1214
+ ? "\nSkeleton inpaint mode: the attached background plate is the fixed final image, and its transparent/open region is the only editable area. Render ONLY the requested foreground content inside that editable region. If the request asks for exact text, CTA, linework, or graphic elements, render those elements only within the editable region. Match the surrounding plate's lighting, perspective, scale, and contact geometry. Do NOT alter, redraw, repeat, restyle, or overlap the plate's existing logo, text, framing, borders, or other brand chrome in the opaque/preserved regions; the plate already contains them. Return the finished plate with the opaque regions preserved."
1215
+ : "";
902
1216
  const diagramInstruction =
903
1217
  input.category === "diagram"
904
1218
  ? "\nDiagram mode: use clear hierarchy, precise labels only when requested, consistent line weights, and enough whitespace for readability."
@@ -916,7 +1230,7 @@ export function compilePrompt(input: {
916
1230
  : intent === "restyle"
917
1231
  ? `The first attached image is the subject to preserve. Keep its identity, pose, composition, and framing. Treat the remaining attached images as style evidence for the brand library. Apply the library look with ${input.styleStrength ?? "balanced"} strength.`
918
1232
  : input.referenceCount > 0
919
- ? `Use the ${input.referenceCount} attached reference image${input.referenceCount === 1 ? "" : "s"} as visual evidence. Treat them by role: style references define visual language, logo/product references define accurate brand/product appearance, subject/source references provide content or composition only, and prior candidates define continuity. Subject/source references must not override the library style brief or custom instructions.`
1233
+ ? `Use the ${input.referenceCount} attached reference image${input.referenceCount === 1 ? "" : "s"} as visual evidence. Treat them by role: style references define visual language, logo/product references define accurate brand/product appearance, background references define fixed composition plates/layout only, mask references define editable regions only, subject/source references provide content or composition only, and prior candidates define continuity. Subject/source/background/mask references must not override the library style brief or custom instructions.`
920
1234
  : "No reference images are attached for this run. Use the style brief and custom instructions as the source of truth.";
921
1235
 
922
1236
  if (intent === "edit") {
@@ -953,7 +1267,7 @@ ${style.texture ? `\nTexture/material treatment: ${style.texture}.` : ""}
953
1267
  ${style.composition ? `\nComposition: ${style.composition}.` : ""}
954
1268
  ${style.lighting ? `\nLighting: ${style.lighting}.` : ""}
955
1269
  ${typographyBlock ? `\n${typographyBlock}` : ""}${frameInstruction}
956
- ${doNot}${logoInstruction}${diagramInstruction}${customInstructions}
1270
+ ${doNot}${logoInstruction}${cutoutInstruction}${backgroundPlateInstruction}${skeletonInpaintInstruction}${diagramInstruction}${customInstructions}
957
1271
 
958
1272
  ${textInstruction}
959
1273
 
@@ -1011,6 +1325,7 @@ export async function selectReferences(input: {
1011
1325
  collectionId?: string | null;
1012
1326
  categories?: ImageCategory[];
1013
1327
  referenceAssetIds?: string[];
1328
+ excludeAssetIds?: string[];
1014
1329
  sourceAssetId?: string;
1015
1330
  subjectAssetId?: string;
1016
1331
  intent?: GenerationIntent;
@@ -1018,6 +1333,7 @@ export async function selectReferences(input: {
1018
1333
  }): Promise<ReferenceForGeneration[]> {
1019
1334
  const db = getDb();
1020
1335
  const requestedExplicitIds = new Set(input.referenceAssetIds ?? []);
1336
+ const excludedAssetIds = new Set(input.excludeAssetIds ?? []);
1021
1337
  let explicitIds = [...requestedExplicitIds];
1022
1338
  if (explicitIds.length) {
1023
1339
  explicitIds = [
@@ -1046,7 +1362,8 @@ export async function selectReferences(input: {
1046
1362
  asset != null &&
1047
1363
  asset.mimeType.startsWith("image/") &&
1048
1364
  asset.status !== "archived" &&
1049
- asset.status !== "failed",
1365
+ asset.status !== "failed" &&
1366
+ !excludedAssetIds.has(asset.id),
1050
1367
  );
1051
1368
  const explicitRefs = await loadReferenceData(
1052
1369
  explicitAssets,
@@ -1081,6 +1398,7 @@ export async function selectReferences(input: {
1081
1398
  libraryId: input.libraryId,
1082
1399
  collectionId: input.collectionId,
1083
1400
  categories: input.categories,
1401
+ excludeAssetIds: input.excludeAssetIds,
1084
1402
  intent: input.intent,
1085
1403
  limit: styleLimit,
1086
1404
  });
@@ -1107,12 +1425,16 @@ export async function selectReferences(input: {
1107
1425
  ? settings.canonicalStyleAssetIds.filter((id) => typeof id === "string")
1108
1426
  : [];
1109
1427
  const candidates = rows
1110
- .filter(
1111
- (asset) =>
1428
+ .filter((asset) => {
1429
+ const metadata = parseJson<{ category?: string }>(asset.metadata, {});
1430
+ return (
1112
1431
  asset.mimeType.startsWith("image/") &&
1113
1432
  asset.status !== "archived" &&
1114
- asset.status !== "failed",
1115
- )
1433
+ asset.status !== "failed" &&
1434
+ metadata.category !== "skeleton" &&
1435
+ !excludedAssetIds.has(asset.id)
1436
+ );
1437
+ })
1116
1438
  .map((asset) => {
1117
1439
  const metadata = parseJson<{
1118
1440
  category?: string;