@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
package/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 2089
31
- - template files: 4676
31
+ - template files: 4797
@@ -1,5 +1,19 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.90.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 74d3e5a: Prevent client fetch storms during high-volume background mutations: `useActionMutation` accepts `skipActionQueryInvalidation` so background mutations can perform narrow invalidation instead of refetching every action query, `useDbSync` accepts `suppressActionInvalidationFor` to skip whole-action-cache invalidation for named high-volume action sync events, and action-query retries are bounded to one attempt for network-level failures (Chrome reports connection-pool exhaustion as a generic fetch failure, so unbounded retries sustained the storm).
8
+ - 16f7429: Defer collaborative rich-markdown seed writes to a timer task so initial Y.Doc seeding does not call `setContent` from React lifecycle.
9
+ - 254f061: Defer agent-authored URL navigation out of React effects to avoid lifecycle flushSync warnings.
10
+ - 69e1e38: Defer collaborative awareness presence updates so editor creation does not trigger React render-time state warnings.
11
+ - 8976a28: Log framework route failures with route, status, message, and development stacks before returning JSON errors.
12
+ - d61ca0c: The collaborative reconcile applies external content on a timer task instead of a microtask, so `setContent` can no longer run inside a React lifecycle flush ("flushSync was called from inside a lifecycle method" console errors during collab reconciliation).
13
+ - 66ffcd9: Treat client-aborted framework route requests as disconnects instead of logging and returning 500 errors.
14
+ - a74a885: `useDbSync` batches consisting entirely of suppressed action events (via `suppressActionInvalidationFor`) now skip the fixed framework invalidation list (extension, slot, tool, and app-state keys) as well as the whole-action-cache invalidation — high-volume background mutations no longer refetch framework queries on every poll tick. Events are still forwarded to `onEvent` and per-source change versions still bump.
15
+ - d44ad4e: Concurrent top-level async transactions on the better-sqlite3 driver are serialized per connection. Previously a transaction starting while another was open saw `inTransaction` and opened a savepoint inside the other task's transaction, which then committed out from under it ("no such savepoint" 500s under concurrent reads/writes). Same-task nesting is detected via AsyncLocalStorage and keeps the direct savepoint path.
16
+
3
17
  ## 0.90.1
4
18
 
5
19
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.90.1",
3
+ "version": "0.90.2",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -2154,7 +2154,7 @@ function URLSync({ browserTabId }: { browserTabId?: string }) {
2154
2154
  // Replace rather than push so repeated agent URL updates don't
2155
2155
  // clutter the history stack and can't trigger extra remounts from
2156
2156
  // router navigation lifecycle.
2157
- navigate(url, { replace: true, flushSync: true });
2157
+ window.setTimeout(() => navigate(url, { replace: true }), 0);
2158
2158
  } catch {
2159
2159
  // Malformed command — ignore.
2160
2160
  }
@@ -307,15 +307,17 @@ export function useCollabReconcile({
307
307
  // Seed only when the shared doc is genuinely empty — either the fragment has
308
308
  // no nodes yet, or it holds no semantic markdown (an empty paragraph, or an
309
309
  // app's sentinel-empty filler via a custom `shouldSeed`).
310
- seededRef.current = true;
311
310
  if (
312
311
  !shouldSeed({ value, currentMarkdown, fragmentLength: fragment.length })
313
312
  ) {
313
+ seededRef.current = true;
314
314
  return;
315
315
  }
316
316
 
317
317
  let cancelled = false;
318
- queueMicrotask(() => {
318
+ // Defer via a timer task (NOT a microtask — microtasks can still run
319
+ // inside React's commit and trigger flushSync-from-lifecycle warnings).
320
+ const seedTimer = setTimeout(() => {
319
321
  if (cancelled || editor.isDestroyed) return;
320
322
  isSettingContentRef.current = true;
321
323
  try {
@@ -329,9 +331,11 @@ export function useCollabReconcile({
329
331
  lastAppliedValueRef.current = value;
330
332
  lastAppliedSerializedRef.current = serialized;
331
333
  if (contentUpdatedAt) lastAppliedUpdatedAtRef.current = contentUpdatedAt;
332
- });
334
+ seededRef.current = true;
335
+ }, 0);
333
336
  return () => {
334
337
  cancelled = true;
338
+ clearTimeout(seedTimer);
335
339
  };
336
340
  }, [
337
341
  collab,
@@ -466,10 +470,10 @@ export function useCollabReconcile({
466
470
  return;
467
471
  }
468
472
 
469
- queueMicrotask(() => {
473
+ const applyTimer = setTimeout(() => {
470
474
  if (cancelled || editor.isDestroyed) return;
471
475
  // Re-check doc-equivalence at apply time. Between the decision above and
472
- // this microtask a peer/Yjs edit (or our own prior apply) may have made
476
+ // this task a peer/Yjs edit (or our own prior apply) may have made
473
477
  // the editor already represent this value — re-applying would be a
474
478
  // wasted setContent that, for non-idempotent input, re-triggers the
475
479
  // loop. Skip when the editor's current serialization already matches the
@@ -521,7 +525,8 @@ export function useCollabReconcile({
521
525
  if (contentUpdatedAt) {
522
526
  lastAppliedUpdatedAtRef.current = contentUpdatedAt;
523
527
  }
524
- });
528
+ }, 0);
529
+ retry = applyTimer;
525
530
  };
526
531
 
527
532
  apply();
@@ -68,9 +68,41 @@ export function defaultActionQueryRetry(
68
68
  // A timeout already made the user wait the full timeout window once;
69
69
  // silently retrying would multiply that wait. Surface it instead.
70
70
  if (isActionTimeout(error)) return false;
71
+ if (isBrowserResourceExhaustion(error)) return false;
72
+ // Network-level failures never carry an HTTP `status` (actionFetch only
73
+ // sets it after a response arrives). Chrome reports connection-pool
74
+ // exhaustion (net::ERR_INSUFFICIENT_RESOURCES) as a generic "Failed to
75
+ // fetch", indistinguishable from a transient blip — allow one retry, not
76
+ // three, so an exhausted tab cannot sustain its own fetch storm.
77
+ if (isNetworkLevelFailure(error)) return failureCount < 1;
71
78
  return failureCount < 3;
72
79
  }
73
80
 
81
+ /** @internal alias kept for existing specs. */
82
+ export const shouldRetryActionQueryForError = defaultActionQueryRetry;
83
+
84
+ function isBrowserResourceExhaustion(error: unknown): boolean {
85
+ const message =
86
+ error instanceof Error
87
+ ? error.message
88
+ : typeof error === "string"
89
+ ? error
90
+ : "";
91
+ return /ERR_INSUFFICIENT_RESOURCES|insufficient resources/i.test(message);
92
+ }
93
+
94
+ function isNetworkLevelFailure(error: unknown): boolean {
95
+ // Match the exact shape actionFetch produces for fetch-level failures (its
96
+ // catch wraps the cause as "Action <name> failed: <cause>" and never sets a
97
+ // status). Other status-less errors (test doubles, transport internals)
98
+ // keep the standard three-retry policy.
99
+ return (
100
+ error instanceof Error &&
101
+ (error as { status?: unknown }).status === undefined &&
102
+ /^Action .+ failed: /.test(error.message)
103
+ );
104
+ }
105
+
74
106
  /**
75
107
  * Default retry backoff for action queries. React Query's stock retryDelay
76
108
  * (1s → 2s → 4s) makes a failing query sit on a spinner for ~7s before the
@@ -436,12 +468,14 @@ export function useActionMutation<
436
468
  "mutationFn"
437
469
  > & {
438
470
  method?: "POST" | "PUT" | "DELETE";
471
+ skipActionQueryInvalidation?: boolean;
439
472
  },
440
473
  ) {
441
474
  const queryClient = useQueryClient();
442
475
  const {
443
476
  method: methodOpt,
444
477
  onSuccess,
478
+ skipActionQueryInvalidation = false,
445
479
  ...restOptions
446
480
  } = options ?? ({} as any);
447
481
  const method = methodOpt ?? "POST";
@@ -454,8 +488,11 @@ export function useActionMutation<
454
488
  mutationFn: (params) =>
455
489
  actionFetch<D>(actionName, method, params as Record<string, any>),
456
490
  onSuccess: (...args: [any, any, any]) => {
457
- // Invalidate related action queries
458
- queryClient.invalidateQueries({ queryKey: ["action"] });
491
+ // Most mutations change app data broadly. High-volume background
492
+ // mutations can opt out and perform narrower invalidation in onSuccess.
493
+ if (!skipActionQueryInvalidation) {
494
+ queryClient.invalidateQueries({ queryKey: ["action"] });
495
+ }
459
496
  (onSuccess as Function)?.(...args);
460
497
  },
461
498
  });
@@ -561,6 +561,9 @@ export function subscribeSyncEvents(
561
561
  * compatibility invalidate triggered by `action` events. Use this to keep
562
562
  * expensive active queries on explicit-refresh semantics while still letting
563
563
  * normal source-versioned queries react through `useChangeVersion`.
564
+ * @param options.suppressActionInvalidationFor - Action names whose sync events
565
+ * should not invalidate all action queries. Use only for high-volume
566
+ * background actions that perform their own narrow client invalidation.
564
567
  */
565
568
  export function useDbSync(
566
569
  options: {
@@ -576,6 +579,7 @@ export function useDbSync(
576
579
  pauseWhenHidden?: boolean;
577
580
  ignoreSource?: string;
578
581
  actionInvalidatePredicate?: (query: Query) => boolean;
582
+ suppressActionInvalidationFor?: string[];
579
583
  } = {},
580
584
  ): void {
581
585
  const {
@@ -599,6 +603,11 @@ export function useDbSync(
599
603
  options.actionInvalidatePredicate,
600
604
  );
601
605
  actionInvalidatePredicateRef.current = options.actionInvalidatePredicate;
606
+ const suppressActionInvalidationForRef = useRef(
607
+ options.suppressActionInvalidationFor,
608
+ );
609
+ suppressActionInvalidationForRef.current =
610
+ options.suppressActionInvalidationFor;
602
611
 
603
612
  useEffect(() => {
604
613
  const id = Symbol("useDbSync");
@@ -621,6 +630,18 @@ export function useDbSync(
621
630
  const relevant = ignore
622
631
  ? events.filter((e) => e.requestSource !== ignore)
623
632
  : events;
633
+ const suppressedActions = new Set(
634
+ suppressActionInvalidationForRef.current ?? [],
635
+ );
636
+ const isSuppressedActionEvent = (evt: SyncEvent) =>
637
+ evt.source === "action" &&
638
+ typeof evt.key === "string" &&
639
+ suppressedActions.has(evt.key);
640
+ const nonAwareness = relevant.filter((e) => e.source !== "awareness");
641
+ const suppressesWholeBatch =
642
+ nonAwareness.length > 0 &&
643
+ nonAwareness.every((evt) => evt.source === "action") &&
644
+ nonAwareness.every(isSuppressedActionEvent);
624
645
 
625
646
  // Bump per-source change counters. Components that read these via
626
647
  // `useChangeVersion(source)` and fold the value into a React Query
@@ -642,7 +663,7 @@ export function useDbSync(
642
663
 
643
664
  if (invalidating.length > 0 && queryClient) {
644
665
  const hasActionEvent = invalidating.some(
645
- (evt) => evt.source === "action",
666
+ (evt) => evt.source === "action" && !isSuppressedActionEvent(evt),
646
667
  );
647
668
  if (hasActionEvent) {
648
669
  // Custom apps frequently start with raw `useQuery` calls before
@@ -661,23 +682,28 @@ export function useDbSync(
661
682
  // etc.). Templates' own data queries do NOT live here — they react
662
683
  // through `useChangeVersion(source)` in their query keys instead, so
663
684
  // a single change event doesn't fan out into "refetch everything".
664
- queryClient.invalidateQueries({ queryKey: ["action"] });
665
- queryClient.invalidateQueries({ queryKey: ["extension"] });
666
- queryClient.invalidateQueries({ queryKey: ["extensions"] });
667
- queryClient.invalidateQueries({ queryKey: ["extension-slots"] });
668
- queryClient.invalidateQueries({ queryKey: ["slot-installs"] });
669
- queryClient.invalidateQueries({ queryKey: ["slot-available"] });
670
- queryClient.invalidateQueries({ queryKey: ["tool"] });
671
- queryClient.invalidateQueries({ queryKey: ["tools"] });
672
- queryClient.invalidateQueries({ queryKey: ["app-state"] });
673
- if (hasAppStateEvent(invalidating, "navigate")) {
674
- queryClient.invalidateQueries({ queryKey: ["navigate-command"] });
675
- }
676
- if (hasAppStateEvent(invalidating, "show-questions")) {
677
- queryClient.invalidateQueries({ queryKey: ["show-questions"] });
678
- }
679
- if (hasAppStateEvent(invalidating, "__set_url__")) {
680
- queryClient.invalidateQueries({ queryKey: ["__set_url__"] });
685
+ // Suppressed-action-only batches skip this whole list (their
686
+ // mutations perform their own narrow invalidation) — but events must
687
+ // STILL reach the onEvent forwarding below, so guard, don't return.
688
+ if (!suppressesWholeBatch) {
689
+ queryClient.invalidateQueries({ queryKey: ["action"] });
690
+ queryClient.invalidateQueries({ queryKey: ["extension"] });
691
+ queryClient.invalidateQueries({ queryKey: ["extensions"] });
692
+ queryClient.invalidateQueries({ queryKey: ["extension-slots"] });
693
+ queryClient.invalidateQueries({ queryKey: ["slot-installs"] });
694
+ queryClient.invalidateQueries({ queryKey: ["slot-available"] });
695
+ queryClient.invalidateQueries({ queryKey: ["tool"] });
696
+ queryClient.invalidateQueries({ queryKey: ["tools"] });
697
+ queryClient.invalidateQueries({ queryKey: ["app-state"] });
698
+ if (hasAppStateEvent(invalidating, "navigate")) {
699
+ queryClient.invalidateQueries({ queryKey: ["navigate-command"] });
700
+ }
701
+ if (hasAppStateEvent(invalidating, "show-questions")) {
702
+ queryClient.invalidateQueries({ queryKey: ["show-questions"] });
703
+ }
704
+ if (hasAppStateEvent(invalidating, "__set_url__")) {
705
+ queryClient.invalidateQueries({ queryKey: ["__set_url__"] });
706
+ }
681
707
  }
682
708
  }
683
709
 
@@ -1,3 +1,5 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+
1
3
  import { drizzle as drizzleD1 } from "drizzle-orm/d1";
2
4
  import type { LibSQLDatabase } from "drizzle-orm/libsql";
3
5
 
@@ -320,15 +322,26 @@ function getBetterSqliteDrizzle() {
320
322
  * The patched transaction also patches the tx object it passes to the callback
321
323
  * so that nested async calls (tx.transaction(async …)) work recursively.
322
324
  */
323
- function patchBetterSqliteTransactions<
325
+ /** @internal exported for the async-tx concurrency spec */
326
+ export function patchBetterSqliteTransactions<
324
327
  DB extends { transaction: (...args: any[]) => any; session: any },
325
328
  >(db: DB, sqlite: { inTransaction: boolean; exec: (sql: string) => void }): DB {
326
329
  let savepointSeq = 0;
330
+ // Concurrent TOP-LEVEL async transactions on the single better-sqlite3
331
+ // connection must not interleave: a second transaction starting while the
332
+ // first is open would see `inTransaction` and open a savepoint INSIDE the
333
+ // first transaction, which then commits out from under it ("no such
334
+ // savepoint"). Serialize top-level transactions through a promise chain;
335
+ // genuine same-task nesting (tx.transaction or db.transaction inside an
336
+ // open callback) is detected via AsyncLocalStorage and keeps the direct
337
+ // savepoint path so it cannot deadlock on the queue.
338
+ const txContext = new AsyncLocalStorage<boolean>();
339
+ let txChain: Promise<unknown> = Promise.resolve();
327
340
 
328
341
  function makeAsyncTransaction(
329
342
  originalTransaction: (...args: any[]) => any,
330
343
  ): (...args: any[]) => Promise<unknown> {
331
- return async function asyncTransaction(
344
+ async function runTransactionBody(
332
345
  cb: (tx: unknown) => unknown,
333
346
  ): Promise<unknown> {
334
347
  // Extract the drizzle tx proxy synchronously — call the original with a
@@ -392,6 +405,23 @@ function patchBetterSqliteTransactions<
392
405
  }
393
406
  throw err;
394
407
  }
408
+ }
409
+
410
+ return function asyncTransaction(
411
+ cb: (tx: unknown) => unknown,
412
+ ): Promise<unknown> {
413
+ if (txContext.getStore()) {
414
+ // Same-task nesting: run directly (savepoint path inside the open
415
+ // transaction). Queueing here would deadlock behind the outer tx.
416
+ return runTransactionBody(cb);
417
+ }
418
+ const run = () => txContext.run(true, () => runTransactionBody(cb));
419
+ const next = txChain.then(run, run);
420
+ txChain = next.then(
421
+ () => undefined,
422
+ () => undefined,
423
+ );
424
+ return next;
395
425
  };
396
426
  }
397
427
 
@@ -364,6 +364,48 @@ function installPluginReadyPlaceholders(
364
364
  }
365
365
  }
366
366
 
367
+ function logFrameworkRouteError(args: {
368
+ method: string | undefined;
369
+ route: string;
370
+ status: number;
371
+ error: unknown;
372
+ }): void {
373
+ const error = args.error as any;
374
+ const message = error?.message || String(args.error);
375
+ const prefix = `[agent-native] ${args.method ?? ""} ${args.route} failed (${args.status})`;
376
+ if (process.env.NODE_ENV === "production") {
377
+ console.error(`${prefix}: ${message}`);
378
+ return;
379
+ }
380
+ console.error(`${prefix}: ${message}`, error?.stack || args.error);
381
+ }
382
+
383
+ function isClientAbortError(error: unknown, event: H3Event): boolean {
384
+ const err = error as any;
385
+ const message = typeof err?.message === "string" ? err.message : "";
386
+ const code = typeof err?.code === "string" ? err.code : "";
387
+ const node = (event as any).node;
388
+ return (
389
+ message === "aborted" ||
390
+ code === "ECONNRESET" ||
391
+ node?.req?.destroyed === true ||
392
+ node?.res?.destroyed === true
393
+ );
394
+ }
395
+
396
+ function debugClientAbort(args: {
397
+ method: string | undefined;
398
+ route: string;
399
+ error: unknown;
400
+ }): void {
401
+ if (process.env.NODE_ENV === "production") return;
402
+ const err = args.error as any;
403
+ const message = err?.message || String(args.error);
404
+ console.debug?.(
405
+ `[agent-native] ${args.method ?? ""} ${args.route} aborted by client: ${message}`,
406
+ );
407
+ }
408
+
367
409
  /**
368
410
  * Await all tracked plugin initializations. Called by the readiness gate
369
411
  * middleware before dispatching framework routes.
@@ -500,10 +542,16 @@ function registerMiddleware(
500
542
  : typeof e?.status === "number"
501
543
  ? e.status
502
544
  : 500;
503
- console.error(
504
- `[agent-native] ${event.method ?? ""} ${reqPath} failed (${status}):`,
505
- e?.stack || e?.message || e,
506
- );
545
+ if (isClientAbortError(err, event)) {
546
+ debugClientAbort({ method: event.method, route: reqPath, error: err });
547
+ return undefined;
548
+ }
549
+ logFrameworkRouteError({
550
+ method: event.method,
551
+ route: reqPath,
552
+ status,
553
+ error: err,
554
+ });
507
555
  // Forward 5xx to server-side Sentry — Nitro's own `error` hook may not
508
556
  // fire here because we convert the throw into a normal JSON response,
509
557
  // and a console.error alone is invisible in deployed environments.
@@ -73,6 +73,11 @@ or generated image/video assets that another app can reference by ID and URL.
73
73
  `assets.metadata.isStyleAnchor`.
74
74
  6. Pass `tier: "fast"` for exploration, `tier: "best"` for final/high-value
75
75
  output, or `tier: "auto"` when there is no clear preference.
76
+ - Model/ratio compatibility: Gemini image models accept any `aspectRatio`, but
77
+ `gpt-image-2` supports only `1:1`, `2:3`, and `3:2`. When the user needs
78
+ another ratio (16:9, 9:16, 4:5, 21:9, …), pick a Gemini model rather than
79
+ `gpt-image-2` — an unsupported pairing is rejected upstream. Source of truth
80
+ is `supportedAspectRatiosForModel` / `MODEL_ASPECT_RATIOS` in `shared/api.ts`.
76
81
  7. Preserve returned `assetId`, `runId`, `previewUrl`, and `downloadUrl`.
77
82
  8. Use `refine-image` for feedback on an existing asset, `edit-image` for
78
83
  targeted changes, and `restyle-image` with `subjectAssetId` and
@@ -11,11 +11,39 @@ LLMs — including Gemini Pro — degrade complex logos. They smear gradients, d
11
11
 
12
12
  1. The library has a `canonicalLogoUrl` (set via `set-canonical-logo --libraryId --assetId`). The asset's role is `logo_reference`.
13
13
  2. **Logo compositing is a preset option.** A generation preset carries `includeLogo` (stored in the preset `settings` and surfaced as a first-class field). When a generation resolves to a preset with `includeLogo: true`, `generate-image` composites the logo. A generate call's own `includeLogo` arg, when passed, overrides the preset for that run; when omitted, the preset's value wins.
14
- 3. When logo compositing is on, the prompt envelope adds:
14
+ 3. **Preset skeletons generalize logo compositing.** A preset can also carry `settings.skeletonSpec`: background first, generated subject second, foreground layers last. If the skeleton already has a `foreground` layer with `source: "canonicalLogo"`, `includeLogo` is treated as a no-op for that run so the logo is not stamped twice. If the skeleton does not include the logo and `includeLogo` is true, the existing canonical-logo layer is appended at the default upper-right position during compositing.
15
+ 4. When logo compositing is on, the prompt envelope adds:
15
16
  > Leave a clean uncluttered area in the upper-right for the real brand logo; do not draw or approximate the logo yourself.
16
- 4. Gemini returns an image with empty space in that corner.
17
- 5. `compositeLogo()` from `server/lib/image-processing.ts` (Sharp) loads the canonical logo PNG / SVG, resizes it to ~16% of the image width with reasonable inset, and composites it onto the generated image.
18
- 6. Output: the image with the actual logo, pixel-perfect, vector-quality if the source is SVG.
17
+ 5. Gemini returns an image with empty space in that corner.
18
+ 6. `compositeLogo()` from `server/lib/image-processing.ts` (Sharp) loads the canonical logo PNG / SVG, resizes it to ~16% of the image width with reasonable inset, and composites it onto the generated image. Skeleton runs use `applyPresetSkeleton()` at the same seam.
19
+ 7. Output: the image with the actual logo, pixel-perfect, vector-quality if the source is SVG.
20
+
21
+ ## Preset skeletons
22
+
23
+ `settings.skeletonSpec` is the prototype storage shape:
24
+
25
+ ```ts
26
+ {
27
+ background: { type: "asset"; assetId: string };
28
+ mask?: { type: "asset"; assetId: string };
29
+ contentMode: "fill" | "cutout";
30
+ contentRegion?: { x: number; y: number; w: number; h: number };
31
+ dropShadow?: boolean;
32
+ foreground?: Array<{
33
+ source: "canonicalLogo" | { assetId: string };
34
+ x: number;
35
+ y: number;
36
+ w: number;
37
+ }>;
38
+ }
39
+ ```
40
+
41
+ - The background is an uploaded or selected brand-library image asset. The compositor cover-fits it to the preset canvas and throws if the asset pixels are unavailable.
42
+ - `fill` drops an opaque generation into the content region, so it works through the normal managed provider path.
43
+ - When the resolved skeleton model is `gpt-image-2`, the action sends a managed edit/inpaint request instead of generate-then-composite: the uploaded plate is the `edit_target`, an optional same-size `mask` asset becomes the editable-area mask, and `maskFromPlateAlpha()` is only the fallback when no manual mask is set. Transparent mask pixels are editable, opaque pixels are sent as preserved regions, and the returned image is final. The manual mask or plate fallback must have transparent pixels.
44
+ - Other `cutout` skeletons ask for an isolated transparent subject, force `gpt-image-1`, attach the background plate as a `background_reference`/composition reference, request `background: "transparent"` through the managed Builder image provider, and only fall back to OpenAI BYOK when the managed provider fails. They clamp only the provider subject ratio to `1:1`, `2:3`, or `3:2`. The final skeleton canvas still uses the preset's requested aspect ratio.
45
+ - The prompt envelope for cutout mode asks for an isolated subject on an empty transparent background. The gpt-image-2 inpaint branch instead asks the model to render the requested foreground content inside the mask's transparent/open region while preserving logos, text, framing, and other opaque plate content.
46
+ - Background and foreground `assetId` values must belong to the selected brand kit and must be images.
19
47
 
20
48
  ## When to use it
21
49
 
@@ -26,8 +54,8 @@ LLMs — including Gemini Pro — degrade complex logos. They smear gradients, d
26
54
  ## When NOT to use it
27
55
 
28
56
  - **Logo on a product** (a t-shirt mockup, a billboard scene, a coffee cup).
29
- Compositing onto a flat corner is fine; compositing onto a curved or perspective surface needs mask-based inpainting that Gemini doesn't expose. v2 will use OpenAI `gpt-image-1`'s edit API for this. v1: tell the user to mock that up in design.
30
- - **Multi-logo scenes** (a partner-logo wall, a footer sponsor row). Same reason. v2.
57
+ Compositing onto a flat corner is fine; compositing onto a curved or perspective surface needs mask-based inpainting. Use a gpt-image-2 skeleton plate with transparent editable regions when that exact layout is required.
58
+ - **Multi-logo scenes** (a partner-logo wall, a footer sponsor row). Same reason: use a prepared plate plus mask-inpaint skeleton, or ask the user to mock it up in design.
31
59
 
32
60
  ## Setting a canonical logo
33
61
 
@@ -43,10 +43,11 @@ prompt, aspectRatio }`.
43
43
  `brand-kit` references to `libraryId`, `preset` references to `presetId`, and
44
44
  `media-type` references to choosing image (`generate-image` /
45
45
  `generate-image-batch`) or video (`generate-video`) generation. The current
46
- library view auto-tags its brand kit as a visible removable chip when the
47
- composer is empty. The image model is the only remaining composer-side
48
- default; the image-model picker writes `imageGenerationModel`, which image
49
- generation actions may use when `model` is omitted.
46
+ library view auto-tags its brand kit as a visible removable chip, and the
47
+ generation preset editor auto-tags both its brand kit and preset. The image
48
+ model is the only remaining composer-side default; the image-model picker
49
+ writes `imageGenerationModel`, which image generation actions may use when
50
+ `model` is omitted.
50
51
  - When a `preset` is tagged, the server embeds that preset's aesthetics and
51
52
  creative philosophy (brand style brief, prompt template, text/logo policy,
52
53
  output format) into your message inside a `<tagged-generation-presets>` block.
@@ -66,6 +67,24 @@ prompt, aspectRatio }`.
66
67
  model never draws the logo itself. Pass `includeLogo` on a generate call only
67
68
  to override the preset for that one run; otherwise omit it. See the
68
69
  `logo-composite` skill.
70
+ - Preset skeletons live in `settings.skeletonSpec` on generation presets and
71
+ are edited from `/brand-kits/:libraryId/presets/:presetId`. They composite a
72
+ generated subject onto an uploaded brand background image and optional
73
+ foreground layers. When the resolved skeleton model is `gpt-image-2`, the
74
+ action uses managed mask inpainting: the uploaded plate is the edit source,
75
+ an optional same-size mask asset controls the editable area, and the plate's
76
+ alpha channel is used as the fallback mask when no manual mask is set. Opaque
77
+ mask pixels are sent as preserved regions, transparent mask pixels are
78
+ editable, and no local compositing runs. Other `cutout` skeletons force
79
+ `gpt-image-1`, request
80
+ transparent output through the managed Builder image provider, attach the
81
+ plate as a composition/background reference for layout awareness, clamp only
82
+ the provider subject ratio to a supported `gpt-image-1` ratio, and keep the
83
+ final skeleton canvas at the preset's requested ratio. See the
84
+ `logo-composite` skill.
85
+ - Humans can edit an existing generation preset from
86
+ `/brand-kits/:libraryId/presets/:presetId`. Use `navigate` with
87
+ `{ view: "preset", libraryId, presetId }` when sending a user to that editor.
69
88
  - `asset-variants` is the shared live generation tray state. New image
70
89
  candidates should appear there through `generate-image` or
71
90
  `generate-image-batch`; do not invent page-local progress surfaces.
@@ -0,0 +1,48 @@
1
+ import { z } from "zod";
2
+
3
+ const fractionSchema = z.coerce.number().min(0).max(1);
4
+ const positiveFractionSchema = z.coerce.number().min(0.02).max(1);
5
+
6
+ export const presetSkeletonSpecSchema = z.object({
7
+ background: z.object({
8
+ type: z.literal("asset"),
9
+ assetId: z.string().min(1),
10
+ }),
11
+ mask: z
12
+ .object({
13
+ type: z.literal("asset"),
14
+ assetId: z.string().min(1),
15
+ })
16
+ .optional(),
17
+ contentMode: z.enum(["cutout", "fill"]),
18
+ contentRegion: z
19
+ .object({
20
+ x: fractionSchema,
21
+ y: fractionSchema,
22
+ w: positiveFractionSchema,
23
+ h: positiveFractionSchema,
24
+ })
25
+ .optional(),
26
+ dropShadow: z.coerce.boolean().optional(),
27
+ foreground: z
28
+ .array(
29
+ z.object({
30
+ source: z.union([
31
+ z.literal("canonicalLogo"),
32
+ z.object({ assetId: z.string().min(1) }),
33
+ ]),
34
+ x: fractionSchema,
35
+ y: fractionSchema,
36
+ w: positiveFractionSchema,
37
+ }),
38
+ )
39
+ .max(8)
40
+ .optional(),
41
+ });
42
+
43
+ export const generationPresetSettingsSchema = z
44
+ .object({
45
+ includeLogo: z.coerce.boolean().optional(),
46
+ skeletonSpec: presetSkeletonSpecSchema.nullable().optional(),
47
+ })
48
+ .catchall(z.unknown());
@@ -26,9 +26,14 @@ function accessContext(ctx?: AccessCtx) {
26
26
  }
27
27
 
28
28
  export async function requireLibrary(id: string, ctx?: AccessCtx) {
29
+ const access = await requireLibraryAccess(id, ctx);
30
+ return access.resource;
31
+ }
32
+
33
+ export async function requireLibraryAccess(id: string, ctx?: AccessCtx) {
29
34
  const access = await resolveAccess("asset-library", id, accessContext(ctx));
30
35
  if (!access) throw new Error("Asset library not found or not accessible.");
31
- return access.resource;
36
+ return access;
32
37
  }
33
38
 
34
39
  export async function requireGenerationSessionInLibrary(
@@ -103,6 +108,7 @@ export function serializeLibrary(row: any) {
103
108
  canonicalLogoAssetId: row.canonicalLogoAssetId,
104
109
  coverAssetId: row.coverAssetId,
105
110
  visibility: row.visibility,
111
+ accessRole: typeof row.accessRole === "string" ? row.accessRole : undefined,
106
112
  archivedAt: row.archivedAt ?? null,
107
113
  createdAt: row.createdAt,
108
114
  updatedAt: row.updatedAt,
@@ -362,6 +368,7 @@ export function serializeAsset(
362
368
  row.mediaType ?? (row.mimeType?.startsWith("video/") ? "video" : "image"),
363
369
  role: row.role,
364
370
  status: row.status,
371
+ category: metadata.category ?? null,
365
372
  title: row.title,
366
373
  description: row.description ?? metadata.description ?? null,
367
374
  altText: row.altText,