@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
@@ -0,0 +1,724 @@
1
+ import {
2
+ parseCssColor,
3
+ rgbaToCss,
4
+ withColorOpacity,
5
+ } from "@shared/color-utils";
6
+ import { useEffect, useRef, useState, type ReactNode } from "react";
7
+
8
+ import { Input } from "@/components/ui/input";
9
+ import { Label } from "@/components/ui/label";
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from "@/components/ui/select";
17
+ import { Slider } from "@/components/ui/slider";
18
+
19
+ import type { StyleChangeMeta } from "../EditPanel";
20
+ import {
21
+ DesignColorPicker,
22
+ type DesignFillRowPatch,
23
+ type DesignGradientStop,
24
+ type DesignGradientStopPatch,
25
+ type DesignGradientType,
26
+ type ImageFillValue,
27
+ } from "../inspector";
28
+ import type { DesignPaintType } from "../inspector/DesignColorPicker";
29
+ import type { GlslShaderPanelContext } from "../inspector/GlslShaderPanel";
30
+ import {
31
+ buildFillRows,
32
+ buildGradientLayer,
33
+ defaultGradientLayer,
34
+ defaultGradientStops,
35
+ fillLayerId,
36
+ fillLayerIndex,
37
+ joinCssLayers,
38
+ parseGradientLayer,
39
+ SOLID_FILL_ID,
40
+ splitCssLayers,
41
+ } from "./fill-gradient-helpers";
42
+ import { colorHasVisibleAlpha, cssColorOrFallback } from "./position-helpers";
43
+ import { isMixedValue, MIXED_VALUE } from "./selection-helpers";
44
+
45
+ function normalizeLengthValue(raw: string, defaultUnit: string): string | null {
46
+ const trimmed = raw.trim();
47
+ // Empty / invalid input returns null so the caller reverts the field instead
48
+ // of committing an empty or garbage CSS value (e.g. fontSize:"" or
49
+ // flexBasis:"abc") to the element's inline style.
50
+ if (!trimmed) return null;
51
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) return `${trimmed}${defaultUnit}`;
52
+ // Validate free-form CSS so junk text never reaches the style. Fall back to
53
+ // accepting the value when CSS.supports is unavailable (SSR/tests) to keep
54
+ // prior behavior in non-DOM environments.
55
+ if (typeof CSS !== "undefined" && typeof CSS.supports === "function") {
56
+ const ok =
57
+ CSS.supports("width", trimmed) ||
58
+ CSS.supports("font-size", trimmed) ||
59
+ CSS.supports("flex-basis", trimmed);
60
+ return ok ? trimmed : null;
61
+ }
62
+ return trimmed;
63
+ }
64
+
65
+ /** Compact input row: label + text input.
66
+ *
67
+ * For CSS length fields (font-size, padding, width, etc.) pass `defaultUnit`
68
+ * so the change is committed on blur/Enter and a bare number auto-appends the
69
+ * unit. Without that, intermediate keystrokes apply invalid CSS — typing "32"
70
+ * for a font-size silently fails because "32" alone isn't a valid length, and
71
+ * it never reaches "32px" because every keystroke re-applies the broken
72
+ * value.
73
+ */
74
+ export function PropInput({
75
+ label,
76
+ value,
77
+ onChange,
78
+ placeholder,
79
+ type = "text",
80
+ defaultUnit,
81
+ }: {
82
+ label: string;
83
+ value: string;
84
+ onChange: (value: string) => void;
85
+ placeholder?: string;
86
+ type?: string;
87
+ defaultUnit?: string;
88
+ }) {
89
+ const [draft, setDraft] = useState(value);
90
+ const mixed = isMixedValue(value);
91
+ // Escape reverts and blurs; the blur handler must then skip its commit or it
92
+ // would re-commit the stale draft closure (mirrors ScrubInput's Escape path).
93
+ const skipNextBlurCommitRef = useRef(false);
94
+
95
+ useEffect(() => {
96
+ setDraft(value);
97
+ }, [value]);
98
+
99
+ const commit = () => {
100
+ if (isMixedValue(draft)) return;
101
+ if (defaultUnit === undefined) {
102
+ if (draft !== value) onChange(draft);
103
+ return;
104
+ }
105
+ const next = normalizeLengthValue(draft, defaultUnit);
106
+ if (next === null) {
107
+ // Invalid or empty — revert the field to the last committed value.
108
+ setDraft(value);
109
+ return;
110
+ }
111
+ if (next !== draft) setDraft(next);
112
+ if (next !== value) onChange(next);
113
+ };
114
+
115
+ return (
116
+ <div className="flex items-center gap-1.5">
117
+ <FieldLabel>{label}</FieldLabel>
118
+ <Input
119
+ type={type}
120
+ value={draft}
121
+ onFocus={(e) => {
122
+ if (mixed) e.currentTarget.select();
123
+ }}
124
+ onChange={(e) => {
125
+ setDraft(e.target.value);
126
+ // For length fields, defer the live update until blur/Enter so that
127
+ // invalid intermediate strings ("3", "32", "32p") don't get applied
128
+ // and discarded by the browser. Free-text fields (without
129
+ // defaultUnit) keep the responsive live-update behavior.
130
+ if (defaultUnit === undefined) onChange(e.target.value);
131
+ }}
132
+ onBlur={() => {
133
+ if (skipNextBlurCommitRef.current) {
134
+ skipNextBlurCommitRef.current = false;
135
+ return;
136
+ }
137
+ commit();
138
+ }}
139
+ onKeyDown={(e) => {
140
+ if (e.key === "Enter") {
141
+ e.preventDefault();
142
+ commit();
143
+ (e.currentTarget as HTMLInputElement).blur();
144
+ return;
145
+ }
146
+ if (e.key === "Escape") {
147
+ e.preventDefault();
148
+ // Revert the draft to the last committed value and blur, matching
149
+ // ScrubInput's Escape behavior.
150
+ setDraft(value);
151
+ skipNextBlurCommitRef.current = true;
152
+ (e.currentTarget as HTMLInputElement).blur();
153
+ }
154
+ }}
155
+ placeholder={placeholder}
156
+ className="h-6 min-w-0 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 !text-[11px] shadow-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)] md:!text-[11px]"
157
+ />
158
+ </div>
159
+ );
160
+ }
161
+
162
+ /** Compact color input: label + design-editor picker popover. */
163
+ export function ColorInput({
164
+ label,
165
+ value,
166
+ onChange,
167
+ backgroundImage,
168
+ backgroundSize,
169
+ backgroundRepeat,
170
+ backgroundPosition,
171
+ onBackgroundImageChange,
172
+ onImageFillChange,
173
+ blendMode,
174
+ onBlendModeChange,
175
+ supportsLayeredFills = false,
176
+ documentColors,
177
+ supportedPaintTypes,
178
+ pickerKey,
179
+ glslShaderContext,
180
+ }: {
181
+ label: string;
182
+ value: string;
183
+ onChange: (value: string, meta?: StyleChangeMeta) => void;
184
+ backgroundImage?: string;
185
+ backgroundSize?: string;
186
+ backgroundRepeat?: string;
187
+ backgroundPosition?: string;
188
+ onBackgroundImageChange?: (value: string) => void;
189
+ onImageFillChange?: (value: ImageFillValue) => void;
190
+ blendMode?: string;
191
+ onBlendModeChange?: (value: string) => void;
192
+ supportsLayeredFills?: boolean;
193
+ /** Hex strings already in use on the page — forwarded to the color picker swatch grid. */
194
+ documentColors?: string[];
195
+ /**
196
+ * Restricts which paint-type tabs the popover renders. Omit for the full
197
+ * set (solid + gradients + image + …). Pass `["solid"]` for properties
198
+ * with no clean gradient/image equivalent (e.g. CSS border/outline
199
+ * strokes) so the tab is hidden instead of clickable-but-discarded.
200
+ */
201
+ supportedPaintTypes?: DesignPaintType[];
202
+ pickerKey?: string;
203
+ /**
204
+ * Persistence context for the code-backed GLSL Shader paint type. When
205
+ * provided, the picker's Shader tab opens the GlslShaderPanel (Created by
206
+ * you / Create new (AI) / Presets) which persists real GLSL source into
207
+ * the screen HTML. Omit to fall back to the legacy shader presets panel.
208
+ */
209
+ glslShaderContext?: GlslShaderPanelContext;
210
+ }) {
211
+ const [draft, setDraft] = useState(value);
212
+ const [selectedFillId, setSelectedFillId] = useState(SOLID_FILL_ID);
213
+ const [selectedStopId, setSelectedStopId] = useState<string | undefined>();
214
+
215
+ useEffect(() => {
216
+ setDraft(value);
217
+ }, [value]);
218
+
219
+ const backgroundLayers = splitCssLayers(backgroundImage || "");
220
+ const backgroundSizeLayers = splitCssLayers(backgroundSize || "");
221
+ const backgroundRepeatLayers = splitCssLayers(backgroundRepeat || "");
222
+ const backgroundPositionLayers = splitCssLayers(backgroundPosition || "");
223
+ const selectedLayerIndex = fillLayerIndex(selectedFillId);
224
+ const selectedGradient =
225
+ selectedLayerIndex !== null
226
+ ? parseGradientLayer(backgroundLayers[selectedLayerIndex] || "")
227
+ : null;
228
+ const fallbackGradientIndex = backgroundLayers.findIndex((layer) =>
229
+ Boolean(parseGradientLayer(layer)),
230
+ );
231
+ const activeGradientIndex =
232
+ selectedGradient && selectedLayerIndex !== null
233
+ ? selectedLayerIndex
234
+ : fallbackGradientIndex >= 0
235
+ ? fallbackGradientIndex
236
+ : null;
237
+ const activeGradient =
238
+ activeGradientIndex !== null
239
+ ? parseGradientLayer(backgroundLayers[activeGradientIndex] || "")
240
+ : null;
241
+ const activeStopIds =
242
+ activeGradient?.stops.map((stop) => stop.id).join("|") ?? "";
243
+
244
+ useEffect(() => {
245
+ if (
246
+ selectedFillId !== SOLID_FILL_ID &&
247
+ (selectedLayerIndex === null ||
248
+ selectedLayerIndex >= backgroundLayers.length)
249
+ ) {
250
+ setSelectedFillId(SOLID_FILL_ID);
251
+ }
252
+ }, [backgroundLayers.length, selectedFillId, selectedLayerIndex]);
253
+
254
+ useEffect(() => {
255
+ if (!activeStopIds) {
256
+ if (selectedStopId) setSelectedStopId(undefined);
257
+ return;
258
+ }
259
+ const stopIds = activeStopIds.split("|").filter(Boolean);
260
+ if (!selectedStopId || !stopIds.includes(selectedStopId)) {
261
+ setSelectedStopId(stopIds[0]);
262
+ }
263
+ }, [activeStopIds, selectedStopId]);
264
+
265
+ // PF12: `phase` defaults to "commit" so every discrete/one-shot caller
266
+ // (swatch clicks, paint-type switches, hex commit, fill-row edits) keeps
267
+ // committing immediately as before. Only the raw per-tick `onChange` wired
268
+ // to DesignColorPicker below passes "preview" explicitly — the picker's own
269
+ // `onChangeComplete` re-invokes setNext with the same final value tagged
270
+ // "commit" once the gesture ends (see the DesignColorPicker render below).
271
+ const setNext = (next: string, phase: "preview" | "commit" = "commit") => {
272
+ // Guard rail for callers that don't wire onBackgroundImageChange (i.e.
273
+ // supportsLayeredFills is false, e.g. the text-fill "color" row): the
274
+ // picker manages gradient/image paint-type selection as *local* UI state
275
+ // independent of props (see DesignColorPicker's localPaintType), so a
276
+ // user can still open the Gradient/Image tab there even when this
277
+ // ColorInput never offered layered fills. When that happens,
278
+ // emitPaintValue falls back to this onChange with a full gradient/url()
279
+ // CSS string, which is invalid for a plain color property (color /
280
+ // backgroundColor) and gets silently dropped by the browser — but not
281
+ // before clobbering the last-known-good value in this component's own
282
+ // state. Reject anything that doesn't parse as a plain solid color in
283
+ // that case instead of forwarding it.
284
+ if (!supportsLayeredFills && !parseCssColor(next)) return;
285
+ setDraft(next);
286
+ onChange(next, { phase });
287
+ };
288
+
289
+ const replaceBackgroundLayer = (index: number, nextLayer: string) => {
290
+ if (!onBackgroundImageChange) return;
291
+ const nextLayers = [...backgroundLayers];
292
+ nextLayers[index] = nextLayer;
293
+ onBackgroundImageChange(joinCssLayers(nextLayers));
294
+ };
295
+
296
+ const removeBackgroundLayer = (index: number) => {
297
+ if (!onBackgroundImageChange) return;
298
+ const nextLayers = backgroundLayers.filter(
299
+ (_, layerIndex) => layerIndex !== index,
300
+ );
301
+ onBackgroundImageChange(joinCssLayers(nextLayers));
302
+ setSelectedFillId(SOLID_FILL_ID);
303
+ };
304
+
305
+ const handlePaintValueChange = (nextValue: string) => {
306
+ if (!supportsLayeredFills || !onBackgroundImageChange) {
307
+ setNext(nextValue);
308
+ return;
309
+ }
310
+
311
+ const selectedLayer = fillLayerIndex(selectedFillId);
312
+ if (selectedLayer !== null) {
313
+ replaceBackgroundLayer(selectedLayer, nextValue);
314
+ const gradient = parseGradientLayer(nextValue);
315
+ if (gradient) setSelectedStopId(gradient.stops[0]?.id);
316
+ return;
317
+ }
318
+
319
+ onBackgroundImageChange(joinCssLayers([nextValue, ...backgroundLayers]));
320
+ setSelectedFillId(fillLayerId(0));
321
+ const gradient = parseGradientLayer(nextValue);
322
+ setSelectedStopId(gradient?.stops[0]?.id);
323
+ };
324
+
325
+ const fillRows = supportsLayeredFills
326
+ ? buildFillRows(
327
+ draft || value || "#000000",
328
+ backgroundLayers,
329
+ selectedFillId,
330
+ )
331
+ : undefined;
332
+
333
+ const handleFillChange = (id: string, patch: DesignFillRowPatch) => {
334
+ if (id === SOLID_FILL_ID) {
335
+ if (patch.value !== undefined) setNext(patch.value);
336
+ if (patch.opacity !== undefined) {
337
+ const parsed = parseCssColor(patch.value ?? draft);
338
+ if (parsed) setNext(rgbaToCss(withColorOpacity(parsed, patch.opacity)));
339
+ }
340
+ return;
341
+ }
342
+
343
+ const index = fillLayerIndex(id);
344
+ if (index === null || !onBackgroundImageChange) return;
345
+ const currentLayer = backgroundLayers[index] || "";
346
+ if (patch.value !== undefined) {
347
+ replaceBackgroundLayer(index, patch.value);
348
+ return;
349
+ }
350
+ if (patch.opacity === undefined) return;
351
+ const gradient = parseGradientLayer(currentLayer);
352
+ if (!gradient) return;
353
+ replaceBackgroundLayer(
354
+ index,
355
+ buildGradientLayer(
356
+ gradient.type,
357
+ gradient.stops.map((stop) => ({
358
+ ...stop,
359
+ opacity: patch.opacity,
360
+ })),
361
+ gradient.prefix,
362
+ ),
363
+ );
364
+ };
365
+
366
+ const handleAddFill = onBackgroundImageChange
367
+ ? () => {
368
+ const nextLayers = [
369
+ defaultGradientLayer("linear", draft || value || "#000000"),
370
+ ...backgroundLayers,
371
+ ];
372
+ onBackgroundImageChange(joinCssLayers(nextLayers));
373
+ setSelectedFillId(fillLayerId(0));
374
+ setSelectedStopId("stop-0");
375
+ }
376
+ : undefined;
377
+
378
+ const handleRemoveFill = onBackgroundImageChange
379
+ ? (id: string) => {
380
+ const index = fillLayerIndex(id);
381
+ if (index === null) return;
382
+ removeBackgroundLayer(index);
383
+ }
384
+ : undefined;
385
+
386
+ const handleGradientTypeChange =
387
+ activeGradient && activeGradientIndex !== null
388
+ ? (type: DesignGradientType) => {
389
+ replaceBackgroundLayer(
390
+ activeGradientIndex,
391
+ buildGradientLayer(type, activeGradient.stops),
392
+ );
393
+ }
394
+ : undefined;
395
+
396
+ const handleGradientStopChange =
397
+ activeGradient && activeGradientIndex !== null
398
+ ? (id: string, patch: DesignGradientStopPatch) => {
399
+ const nextStops = activeGradient.stops.map((stop) =>
400
+ stop.id === id ? { ...stop, ...patch } : stop,
401
+ );
402
+ replaceBackgroundLayer(
403
+ activeGradientIndex,
404
+ buildGradientLayer(
405
+ activeGradient.type,
406
+ nextStops,
407
+ activeGradient.prefix,
408
+ ),
409
+ );
410
+ }
411
+ : undefined;
412
+
413
+ const handleAddGradientStop = onBackgroundImageChange
414
+ ? () => {
415
+ if (activeGradient && activeGradientIndex !== null) {
416
+ const nextStop: DesignGradientStop = {
417
+ id: `stop-${activeGradient.stops.length}`,
418
+ color: draft || "#000000",
419
+ position: 50,
420
+ opacity: 100,
421
+ };
422
+ replaceBackgroundLayer(
423
+ activeGradientIndex,
424
+ buildGradientLayer(
425
+ activeGradient.type,
426
+ [...activeGradient.stops, nextStop],
427
+ activeGradient.prefix,
428
+ ),
429
+ );
430
+ setSelectedStopId(nextStop.id);
431
+ return;
432
+ }
433
+
434
+ onBackgroundImageChange(
435
+ joinCssLayers([
436
+ defaultGradientLayer("linear", draft || value || "#000000"),
437
+ ...backgroundLayers,
438
+ ]),
439
+ );
440
+ setSelectedFillId(fillLayerId(0));
441
+ setSelectedStopId("stop-0");
442
+ }
443
+ : undefined;
444
+
445
+ const handleRemoveGradientStop =
446
+ activeGradient && activeGradientIndex !== null
447
+ ? (id: string) => {
448
+ if (activeGradient.stops.length <= 2) return;
449
+ const nextStops = activeGradient.stops.filter(
450
+ (stop) => stop.id !== id,
451
+ );
452
+ replaceBackgroundLayer(
453
+ activeGradientIndex,
454
+ buildGradientLayer(
455
+ activeGradient.type,
456
+ nextStops,
457
+ activeGradient.prefix,
458
+ ),
459
+ );
460
+ setSelectedStopId(nextStops[0]?.id);
461
+ }
462
+ : undefined;
463
+
464
+ const selectedPaintType: DesignPaintType =
465
+ selectedFillId !== SOLID_FILL_ID
466
+ ? selectedGradient
467
+ ? selectedGradient.type
468
+ : "image"
469
+ : colorHasVisibleAlpha(draft || value)
470
+ ? "solid"
471
+ : "none";
472
+ const pickerValue =
473
+ selectedLayerIndex !== null
474
+ ? (backgroundLayers[selectedLayerIndex] ?? draft ?? value ?? "#000000")
475
+ : draft || "#000000";
476
+ const selectedBackgroundLayerValue = (layers: string[]): string | undefined =>
477
+ selectedLayerIndex !== null ? layers[selectedLayerIndex] : undefined;
478
+ const handlePaintTypeChange = (type: DesignPaintType) => {
479
+ const selectedLayer = fillLayerIndex(selectedFillId);
480
+ if (type === "solid") {
481
+ if (selectedLayer !== null) removeBackgroundLayer(selectedLayer);
482
+ setSelectedFillId(SOLID_FILL_ID);
483
+ setNext(cssColorOrFallback(draft || value, "#000000"));
484
+ return;
485
+ }
486
+ if (type === "none") {
487
+ if (selectedLayer !== null) {
488
+ removeBackgroundLayer(selectedLayer);
489
+ return;
490
+ }
491
+ setNext("transparent");
492
+ return;
493
+ }
494
+ if (!onBackgroundImageChange) return;
495
+
496
+ if (
497
+ type !== "linear" &&
498
+ type !== "radial" &&
499
+ type !== "angular" &&
500
+ type !== "diamond"
501
+ ) {
502
+ return;
503
+ }
504
+ const nextType: DesignGradientType = type;
505
+ const layerIndex = selectedLayer ?? activeGradientIndex;
506
+ if (layerIndex !== null) {
507
+ const currentGradient = parseGradientLayer(
508
+ backgroundLayers[layerIndex] || "",
509
+ );
510
+ const stops =
511
+ currentGradient?.stops ?? defaultGradientStops(draft || value);
512
+ replaceBackgroundLayer(layerIndex, buildGradientLayer(nextType, stops));
513
+ setSelectedFillId(fillLayerId(layerIndex));
514
+ setSelectedStopId(stops[0]?.id);
515
+ return;
516
+ }
517
+
518
+ onBackgroundImageChange(
519
+ joinCssLayers([
520
+ defaultGradientLayer(nextType, draft || value || "#000000"),
521
+ ...backgroundLayers,
522
+ ]),
523
+ );
524
+ setSelectedFillId(fillLayerId(0));
525
+ setSelectedStopId("stop-0");
526
+ };
527
+
528
+ if (isMixedValue(value)) {
529
+ return (
530
+ <button
531
+ type="button"
532
+ className="flex h-6 w-full items-center rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-left !text-[11px] text-muted-foreground"
533
+ onClick={() => onChange("#000000")}
534
+ >
535
+ {MIXED_VALUE}
536
+ </button>
537
+ );
538
+ }
539
+
540
+ return (
541
+ <DesignColorPicker
542
+ key={pickerKey}
543
+ label={label}
544
+ value={pickerValue}
545
+ // PF12: `onChange` fires on every SV/hue/alpha drag tick — tag those as
546
+ // "preview" so the caller can skip the expensive source commit and only
547
+ // update the live iframe preview. `onChangeComplete` fires exactly once
548
+ // per gesture (drag-end, hex commit, keyboard nudge, swatch click,
549
+ // paint-type switch) with the same final value, tagged "commit" so the
550
+ // authoritative source write always happens exactly once.
551
+ onChange={(v) => setNext(v, "preview")}
552
+ onChangeComplete={(v) => setNext(v, "commit")}
553
+ onPaintValueChange={
554
+ supportsLayeredFills ? handlePaintValueChange : undefined
555
+ }
556
+ onImageFillChange={onImageFillChange}
557
+ backgroundImage={selectedBackgroundLayerValue(backgroundLayers)}
558
+ backgroundSize={selectedBackgroundLayerValue(backgroundSizeLayers)}
559
+ backgroundRepeat={selectedBackgroundLayerValue(backgroundRepeatLayers)}
560
+ backgroundPosition={selectedBackgroundLayerValue(
561
+ backgroundPositionLayers,
562
+ )}
563
+ blendMode={blendMode}
564
+ onBlendModeChange={onBlendModeChange}
565
+ showBlendMode={Boolean(onBlendModeChange)}
566
+ fillRows={fillRows}
567
+ selectedFillId={selectedFillId}
568
+ onFillSelect={supportsLayeredFills ? setSelectedFillId : undefined}
569
+ onFillChange={supportsLayeredFills ? handleFillChange : undefined}
570
+ onAddFill={supportsLayeredFills ? handleAddFill : undefined}
571
+ onRemoveFill={supportsLayeredFills ? handleRemoveFill : undefined}
572
+ paintType={selectedPaintType}
573
+ onPaintTypeChange={handlePaintTypeChange}
574
+ gradientType={activeGradient?.type}
575
+ onGradientTypeChange={handleGradientTypeChange}
576
+ gradientStops={activeGradient?.stops}
577
+ selectedStopId={selectedStopId}
578
+ onGradientStopSelect={setSelectedStopId}
579
+ onGradientStopChange={handleGradientStopChange}
580
+ onAddGradientStop={
581
+ supportsLayeredFills ? handleAddGradientStop : undefined
582
+ }
583
+ onRemoveGradientStop={handleRemoveGradientStop}
584
+ documentColors={documentColors}
585
+ supportedPaintTypes={supportedPaintTypes}
586
+ glslShaderContext={glslShaderContext}
587
+ />
588
+ );
589
+ }
590
+
591
+ /** Select dropdown */
592
+ export function PropSelect({
593
+ label,
594
+ value,
595
+ onChange,
596
+ options,
597
+ }: {
598
+ label: string;
599
+ value: string;
600
+ onChange: (value: string) => void;
601
+ options: { value: string; label: string }[];
602
+ }) {
603
+ return (
604
+ <div className="flex items-center gap-1.5">
605
+ <FieldLabel>{label}</FieldLabel>
606
+ <Select value={value} onValueChange={onChange}>
607
+ <SelectTrigger className="h-6 min-w-0 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 !text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]">
608
+ <SelectValue />
609
+ </SelectTrigger>
610
+ <SelectContent>
611
+ {options.map((opt) => (
612
+ <SelectItem
613
+ key={opt.value}
614
+ value={opt.value}
615
+ className="!text-[11px]"
616
+ >
617
+ {opt.label}
618
+ </SelectItem>
619
+ ))}
620
+ </SelectContent>
621
+ </Select>
622
+ </div>
623
+ );
624
+ }
625
+
626
+ /** Slider with label and value display */
627
+ export function PropSlider({
628
+ label,
629
+ value,
630
+ onChange,
631
+ min = 0,
632
+ max = 100,
633
+ step = 1,
634
+ unit = "",
635
+ }: {
636
+ label: string;
637
+ value: number;
638
+ onChange: (value: number) => void;
639
+ min?: number;
640
+ max?: number;
641
+ step?: number;
642
+ unit?: string;
643
+ }) {
644
+ return (
645
+ <div className="flex items-center gap-1.5">
646
+ <FieldLabel>{label}</FieldLabel>
647
+ <Slider
648
+ value={[value]}
649
+ onValueChange={([v]) => onChange(v)}
650
+ min={min}
651
+ max={max}
652
+ step={step}
653
+ className="flex-1"
654
+ />
655
+ <span className="w-12 text-right !text-[11px] tabular-nums text-muted-foreground">
656
+ {value}
657
+ {unit}
658
+ </span>
659
+ </div>
660
+ );
661
+ }
662
+
663
+ /**
664
+ * design-editor inspector section. Matches the design editor "Design" panel chrome:
665
+ * - NO left collapse chevron (the design editor uses none).
666
+ * - A thin divider line above each section.
667
+ * - A bold left-aligned title.
668
+ * - Right-aligned action icons (add layer, toggles, styles, etc.).
669
+ *
670
+ * The title is still clickable to collapse the body (design sections collapse
671
+ * on title click) but renders no chevron glyph, just the same way.
672
+ */
673
+ export function PanelSection({
674
+ title,
675
+ actions,
676
+ children,
677
+ defaultCollapsed = false,
678
+ }: {
679
+ title: string;
680
+ actions?: ReactNode;
681
+ children?: ReactNode;
682
+ defaultCollapsed?: boolean;
683
+ }) {
684
+ const [collapsed, setCollapsed] = useState(defaultCollapsed);
685
+
686
+ return (
687
+ <section className="shrink-0 border-t border-[var(--design-editor-control-border)] first:border-t-0">
688
+ <div className="flex min-h-9 items-center gap-2 px-3">
689
+ <button
690
+ type="button"
691
+ className="flex min-w-0 flex-1 cursor-pointer items-center bg-transparent text-left"
692
+ onClick={() => setCollapsed((c) => !c)}
693
+ aria-expanded={!collapsed}
694
+ >
695
+ <h3 className="min-w-0 flex-1 truncate !text-[11px] font-semibold text-foreground">
696
+ {title}
697
+ </h3>
698
+ </button>
699
+ {actions ? (
700
+ <div className="flex shrink-0 items-center gap-0.5">{actions}</div>
701
+ ) : null}
702
+ </div>
703
+ {!collapsed && children ? (
704
+ <div className="space-y-1.5 px-3 pb-3 pt-0.5 !text-[11px]">
705
+ {children}
706
+ </div>
707
+ ) : null}
708
+ </section>
709
+ );
710
+ }
711
+
712
+ export function FieldLabel({ children }: { children: ReactNode }) {
713
+ return (
714
+ <Label className="w-[64px] shrink-0 !text-[11px] font-medium text-muted-foreground">
715
+ {children}
716
+ </Label>
717
+ );
718
+ }
719
+
720
+ export function SubsectionLabel({ children }: { children: ReactNode }) {
721
+ return (
722
+ <p className="!text-[11px] font-medium text-muted-foreground">{children}</p>
723
+ );
724
+ }