@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,780 @@
1
+ import { useT } from "@agent-native/core/client";
2
+ import {
3
+ parseCssColor,
4
+ rgbaToCss,
5
+ withColorOpacity,
6
+ } from "@shared/color-utils";
7
+ import {
8
+ IconBackground,
9
+ IconBlur,
10
+ IconEye,
11
+ IconEyeOff,
12
+ IconMinus,
13
+ IconPlus,
14
+ IconShadow,
15
+ IconWaveSine,
16
+ } from "@tabler/icons-react";
17
+ import { useState } from "react";
18
+
19
+ import { Button } from "@/components/ui/button";
20
+ import {
21
+ DropdownMenu,
22
+ DropdownMenuContent,
23
+ DropdownMenuItem,
24
+ DropdownMenuTrigger,
25
+ } from "@/components/ui/dropdown-menu";
26
+ import {
27
+ Popover,
28
+ PopoverContent,
29
+ PopoverTrigger,
30
+ } from "@/components/ui/popover";
31
+ import { cn } from "@/lib/utils";
32
+
33
+ import { ScrubInput } from "../inspector";
34
+ import {
35
+ GlslShaderEffectSection,
36
+ useScreenGlslShaders,
37
+ type GlslShaderPanelContext,
38
+ } from "../inspector/GlslShaderPanel";
39
+ import type { ElementInfo } from "../types";
40
+ import { elementIdentityKey } from "./element-identity";
41
+ import { FieldTrailer } from "./field-primitives";
42
+ import { splitCssLayers } from "./fill-gradient-helpers";
43
+ import {
44
+ RowDragHandle,
45
+ SectionIconButton,
46
+ useRowDragReorder,
47
+ } from "./inspector-controls";
48
+ import { ColorInput, PanelSection } from "./panel-primitives";
49
+ import {
50
+ colorHasVisibleAlpha,
51
+ compactCssValue,
52
+ cssColorOrFallback,
53
+ swatchStyle,
54
+ } from "./position-helpers";
55
+ import type {
56
+ MotionKeyframeFieldContext,
57
+ StyleChangeHandler,
58
+ StyleChangeMeta,
59
+ StylesChangeHandler,
60
+ } from "./style-change-types";
61
+
62
+ interface ShadowLayer {
63
+ id: string;
64
+ x: number;
65
+ y: number;
66
+ blur: number;
67
+ spread: number;
68
+ color: string;
69
+ inset: boolean;
70
+ }
71
+
72
+ function defaultDropShadowLayer(index: number): ShadowLayer {
73
+ return {
74
+ id: `shadow-${index}`,
75
+ x: 0,
76
+ y: 4,
77
+ blur: 12,
78
+ spread: 0,
79
+ color: "rgba(0, 0, 0, 0.25)",
80
+ inset: false,
81
+ };
82
+ }
83
+
84
+ function parseShadowLayers(value: string | undefined): ShadowLayer[] {
85
+ return splitCssLayers(value || "")
86
+ .filter((layer) => layer && layer !== "none")
87
+ .map((layer, index) => parseShadowLayer(layer, index));
88
+ }
89
+
90
+ function parseShadowLayer(layer: string, index: number): ShadowLayer {
91
+ const tokens = splitCssTokens(layer);
92
+ const inset = tokens.includes("inset");
93
+ const colorToken =
94
+ tokens.find((token) => parseCssColor(token) || token === "transparent") ??
95
+ // Preserve a color we don't parse into RGBA (currentColor, var(--x), or any
96
+ // unrecognized keyword): the color is the non-inset token that doesn't look
97
+ // like a numeric length. Without this, tweaking x/y/blur would reset it to
98
+ // the hardcoded default below.
99
+ tokens.find((token) => token !== "inset" && !/^[-+]?[\d.]/.test(token)) ??
100
+ "rgba(0, 0, 0, 0.25)";
101
+ const numericTokens = tokens
102
+ .filter((token) => token !== "inset" && token !== colorToken)
103
+ .map((token) => parseFloat(token))
104
+ .filter((value) => Number.isFinite(value));
105
+
106
+ return {
107
+ id: `shadow-${index}`,
108
+ x: numericTokens[0] ?? 0,
109
+ y: numericTokens[1] ?? 4,
110
+ blur: numericTokens[2] ?? 12,
111
+ spread: numericTokens[3] ?? 0,
112
+ color: colorToken,
113
+ inset,
114
+ };
115
+ }
116
+
117
+ function splitCssTokens(value: string): string[] {
118
+ const tokens: string[] = [];
119
+ let start = 0;
120
+ let depth = 0;
121
+ for (let index = 0; index < value.length; index += 1) {
122
+ const char = value[index];
123
+ if (char === "(") depth += 1;
124
+ if (char === ")") depth = Math.max(0, depth - 1);
125
+ if (/\s/.test(char) && depth === 0) {
126
+ const token = value.slice(start, index).trim();
127
+ if (token) tokens.push(token);
128
+ start = index + 1;
129
+ }
130
+ }
131
+ const finalToken = value.slice(start).trim();
132
+ if (finalToken) tokens.push(finalToken);
133
+ return tokens;
134
+ }
135
+
136
+ function serializeShadowLayers(layers: ShadowLayer[]) {
137
+ if (!layers.length) return "none";
138
+ return layers
139
+ .map((layer) =>
140
+ [
141
+ layer.inset ? "inset" : "",
142
+ `${Math.round(layer.x)}px`,
143
+ `${Math.round(layer.y)}px`,
144
+ `${Math.max(0, Math.round(layer.blur))}px`,
145
+ // Spread radius may legitimately be negative for either inset or
146
+ // drop shadows — only blur-radius is clamped to >= 0 in CSS.
147
+ `${Math.round(layer.spread)}px`,
148
+ layer.color,
149
+ ]
150
+ .filter(Boolean)
151
+ .join(" "),
152
+ )
153
+ .join(", ");
154
+ }
155
+
156
+ function readBlurFilter(value: string | undefined): number {
157
+ const match = value?.match(/blur\((-?\d+(?:\.\d+)?)px\)/);
158
+ return match ? Math.max(0, Number(match[1])) : 0;
159
+ }
160
+
161
+ function hasBlurFilter(value: string | undefined): boolean {
162
+ return /blur\(/.test(value || "");
163
+ }
164
+
165
+ function setBlurFilterValue(value: string | undefined, blur: number): string {
166
+ const blurFn = `blur(${Math.max(0, Math.round(blur))}px)`;
167
+ const existing = compactCssValue(value, "");
168
+ return existing.includes("blur(")
169
+ ? existing.replace(/blur\([^)]*\)/, blurFn)
170
+ : blurFn;
171
+ }
172
+
173
+ function shadowColorWithOpacity(color: string, opacity: number): string {
174
+ const parsed = parseCssColor(color);
175
+ return parsed
176
+ ? rgbaToCss(withColorOpacity(parsed, opacity))
177
+ : opacity <= 0
178
+ ? "rgba(0, 0, 0, 0)"
179
+ : color;
180
+ }
181
+
182
+ function ShadowEffectRow({
183
+ layer,
184
+ index,
185
+ onChange,
186
+ onRemove,
187
+ onToggleVisibility,
188
+ dragHandleLabel,
189
+ dropIndicator,
190
+ rowProps,
191
+ handleProps,
192
+ element,
193
+ motionKeyframeContext,
194
+ }: {
195
+ layer: ShadowLayer;
196
+ index: number;
197
+ onChange: (patch: Partial<ShadowLayer>, meta?: StyleChangeMeta) => void;
198
+ onRemove: () => void;
199
+ onToggleVisibility: () => void;
200
+ dragHandleLabel: string;
201
+ dropIndicator?: "before" | "after" | null;
202
+ rowProps: ReturnType<ReturnType<typeof useRowDragReorder>["getRowProps"]>;
203
+ handleProps: ReturnType<
204
+ ReturnType<typeof useRowDragReorder>["getHandleProps"]
205
+ >;
206
+ /**
207
+ * Optional — only needed for the keyframe diamond (drop shadow's motion
208
+ * track keys the WHOLE `box-shadow` value, so there's one diamond for the
209
+ * layer, not per x/y/blur field). No breakpoint override indicator here —
210
+ * multi-layer `box-shadow` composition isn't covered by
211
+ * `getBreakpointOverrideState`'s per-property model yet.
212
+ */
213
+ element?: ElementInfo;
214
+ motionKeyframeContext?: MotionKeyframeFieldContext;
215
+ }) {
216
+ const t = useT();
217
+ const visible = colorHasVisibleAlpha(layer.color);
218
+ return (
219
+ <Popover>
220
+ {/* design effect row: [grip] [swatch+label+x,y,blur trigger (flex-1)] [eye] [remove] */}
221
+ <div className="group relative flex items-center gap-1.5" {...rowProps}>
222
+ <RowDragHandle
223
+ label={dragHandleLabel}
224
+ dropIndicator={dropIndicator}
225
+ {...handleProps}
226
+ />
227
+ <PopoverTrigger asChild>
228
+ <button
229
+ type="button"
230
+ className="flex h-6 min-w-0 flex-1 items-center gap-1.5 rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-left !text-[11px] hover:bg-[var(--design-editor-panel-raised-bg)]"
231
+ >
232
+ <span
233
+ className="size-4 shrink-0 rounded-sm border border-[var(--design-editor-control-border)]"
234
+ style={swatchStyle(layer.color)}
235
+ />
236
+ <span className="min-w-0 flex-1 truncate font-medium text-foreground">
237
+ {index === 0
238
+ ? t("editPanel.labels.dropShadow")
239
+ : `${t("editPanel.labels.dropShadow")} ${index + 1}`}
240
+ </span>
241
+ <span className="shrink-0 tabular-nums text-muted-foreground">
242
+ {Math.round(layer.x)}, {Math.round(layer.y)},{" "}
243
+ {Math.round(layer.blur)}
244
+ </span>
245
+ </button>
246
+ </PopoverTrigger>
247
+ <SectionIconButton
248
+ label={
249
+ visible
250
+ ? t("editPanel.labels.hideLayer")
251
+ : t("editPanel.labels.showLayer")
252
+ }
253
+ onClick={onToggleVisibility}
254
+ >
255
+ {visible ? (
256
+ <IconEye className="size-3.5" />
257
+ ) : (
258
+ <IconEyeOff className="size-3.5" />
259
+ )}
260
+ </SectionIconButton>
261
+ <SectionIconButton
262
+ label={t("editPanel.labels.removeLayer")}
263
+ onClick={onRemove}
264
+ >
265
+ <IconMinus className="size-3.5" />
266
+ </SectionIconButton>
267
+ {index === 0 && element ? (
268
+ <FieldTrailer
269
+ element={element}
270
+ motionCssProperty="box-shadow"
271
+ motionKeyframeContext={motionKeyframeContext}
272
+ hoverRevealClassName="opacity-0 group-hover:opacity-100"
273
+ />
274
+ ) : null}
275
+ </div>
276
+ <PopoverContent
277
+ side="left"
278
+ align="start"
279
+ sideOffset={8}
280
+ className="w-72 p-3"
281
+ >
282
+ <div className="space-y-3">
283
+ <div className="flex items-center justify-between gap-2">
284
+ <p className="text-xs font-semibold text-foreground">
285
+ {t("editPanel.labels.dropShadow")}
286
+ </p>
287
+ <button
288
+ type="button"
289
+ className={cn(
290
+ "rounded border px-2 py-1 !text-[11px]",
291
+ layer.inset
292
+ ? "border-[var(--design-editor-accent-color)] bg-[var(--design-editor-selection-color)] text-foreground"
293
+ : "border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] text-muted-foreground",
294
+ )}
295
+ onClick={() => onChange({ inset: !layer.inset })}
296
+ >
297
+ {t("editPanel.labels.innerShadow")}
298
+ </button>
299
+ </div>
300
+ <div className="grid grid-cols-2 gap-2">
301
+ <ScrubInput
302
+ label="X"
303
+ value={layer.x}
304
+ onChange={(value, meta) =>
305
+ onChange({ x: value }, { phase: meta?.phase })
306
+ }
307
+ unit="px"
308
+ precision={1}
309
+ inputClassName="h-6"
310
+ />
311
+ <ScrubInput
312
+ label="Y"
313
+ value={layer.y}
314
+ onChange={(value, meta) =>
315
+ onChange({ y: value }, { phase: meta?.phase })
316
+ }
317
+ unit="px"
318
+ precision={1}
319
+ inputClassName="h-6"
320
+ />
321
+ <ScrubInput
322
+ label={t("editPanel.labels.blur")}
323
+ value={layer.blur}
324
+ onChange={(value, meta) =>
325
+ onChange({ blur: Math.max(0, value) }, { phase: meta?.phase })
326
+ }
327
+ unit="px"
328
+ min={0}
329
+ precision={1}
330
+ inputClassName="h-6"
331
+ />
332
+ <ScrubInput
333
+ label={t("editPanel.labels.spread")}
334
+ value={layer.spread}
335
+ // Spread radius is valid negative for both inset AND drop
336
+ // (non-inset) shadows in real CSS — negative spread shrinks
337
+ // the shadow smaller than the box before blurring, a common
338
+ // technique. Only blur-radius must stay >= 0.
339
+ onChange={(value, meta) =>
340
+ onChange({ spread: value }, { phase: meta?.phase })
341
+ }
342
+ unit="px"
343
+ precision={1}
344
+ inputClassName="h-6"
345
+ />
346
+ </div>
347
+ <ColorInput
348
+ label={t("editPanel.labels.color")}
349
+ value={cssColorOrFallback(layer.color, "rgba(0, 0, 0, 0.25)")}
350
+ onChange={(value, meta) => onChange({ color: value }, meta)}
351
+ />
352
+ </div>
353
+ </PopoverContent>
354
+ </Popover>
355
+ );
356
+ }
357
+
358
+ export function EffectsProperties({
359
+ element,
360
+ onStyleChange,
361
+ onStylesChange,
362
+ glslShaderContext,
363
+ motionKeyframeContext,
364
+ }: {
365
+ element: ElementInfo;
366
+ onStyleChange: StyleChangeHandler;
367
+ onStylesChange?: StylesChangeHandler;
368
+ /**
369
+ * Persistence context for the code-backed Shader effect type (GLSL
370
+ * overlay rendered above the element's content, saved into the screen
371
+ * HTML). When absent the Shader entry is hidden from the Add-effect menu.
372
+ */
373
+ glslShaderContext?: GlslShaderPanelContext;
374
+ motionKeyframeContext?: MotionKeyframeFieldContext;
375
+ }) {
376
+ const t = useT();
377
+ const [shaderPickerOpen, setShaderPickerOpen] = useState(false);
378
+ const styles = element.computedStyles;
379
+ const blurValue = readBlurFilter(styles.filter);
380
+ const filterHasBlur = hasBlurFilter(styles.filter);
381
+ // M5 · Background (backdrop) blur is a distinct design effect type, backed by
382
+ // CSS `backdrop-filter: blur()` (vs layer blur's `filter: blur()`).
383
+ const backdropFilterValue =
384
+ styles.backdropFilter || styles.webkitBackdropFilter;
385
+ const backdropFilterHasBlur = hasBlurFilter(backdropFilterValue);
386
+ const backdropBlurValue = readBlurFilter(backdropFilterValue);
387
+ const [hiddenEffectStash, setHiddenEffectStash] = useState<
388
+ Record<string, string>
389
+ >({});
390
+ const effectStashKey = elementIdentityKey(element);
391
+ const layerBlurStashKey = `${effectStashKey}:filter:blur`;
392
+ const backdropBlurStashKey = `${effectStashKey}:backdrop-filter:blur`;
393
+ const shadowLayers = parseShadowLayers(styles.boxShadow);
394
+ const setShadowLayers = (layers: ShadowLayer[], meta?: StyleChangeMeta) => {
395
+ const boxShadow = serializeShadowLayers(layers);
396
+ if (onStylesChange) onStylesChange({ boxShadow }, meta);
397
+ else onStyleChange("boxShadow", boxShadow, meta);
398
+ };
399
+ const addDropShadow = () =>
400
+ setShadowLayers([
401
+ ...shadowLayers,
402
+ defaultDropShadowLayer(shadowLayers.length),
403
+ ]);
404
+ const addLayerBlur = () => onStyleChange("filter", "blur(4px)");
405
+ const addBackgroundBlur = () => onStyleChange("backdropFilter", "blur(8px)");
406
+ const reorderShadowLayers = (from: number, to: number) => {
407
+ const next = [...shadowLayers];
408
+ const [moved] = next.splice(from, 1);
409
+ if (moved === undefined) return;
410
+ next.splice(to, 0, moved);
411
+ setShadowLayers(next);
412
+ };
413
+ const shadowDrag = useRowDragReorder(
414
+ shadowLayers.length,
415
+ reorderShadowLayers,
416
+ );
417
+ // `glslShaderContext?.nodeId` is the SELECTION target's node id — it is set
418
+ // whenever the selected element is a valid shader-effect host, regardless
419
+ // of whether a shader effect actually exists on it yet (it also describes
420
+ // "could add a shader here"). Gating on its mere presence made the section
421
+ // think it had content for every plain element with no effects at all.
422
+ // Look up whether an effect-mode shader is actually MOUNTED on this node
423
+ // (same screen.mounts lookup GlslShaderEffectSection performs internally)
424
+ // so the gate reflects a real effect, not just a selectable target.
425
+ const screenShaders = useScreenGlslShaders(glslShaderContext ?? {});
426
+ const hasShaderEffect = Boolean(
427
+ glslShaderContext?.nodeId &&
428
+ screenShaders.mounts.some(
429
+ (mount) =>
430
+ mount.nodeId === glslShaderContext.nodeId && mount.mode === "effect",
431
+ ),
432
+ );
433
+ // Whether there is anything at all to render below the header row. Each
434
+ // effect kind below is its own top-level sibling conditional (not one
435
+ // single ternary), so when every one of them is empty, JSX would still
436
+ // hand PanelSection a real (truthy) array of `null`s as `children` — its
437
+ // `children &&` guard can't tell that apart from "has content" and renders
438
+ // an empty spacer div under the header. Gating the whole block behind one
439
+ // boolean keeps `children` a real `null` in that case, matching how the
440
+ // other sections (e.g. Fill) stay collapsed-empty.
441
+ // Also true while the shader picker is open (adding a new shader effect,
442
+ // not applied yet) — mirrors GlslShaderEffectSection's own
443
+ // `!effectMount && !pickerOpen` early-return so opening the picker doesn't
444
+ // get swallowed by this outer gate before it can render itself.
445
+ const hasEffectsContent =
446
+ shadowLayers.length > 0 ||
447
+ filterHasBlur ||
448
+ backdropFilterHasBlur ||
449
+ hasShaderEffect ||
450
+ shaderPickerOpen;
451
+
452
+ return (
453
+ <PanelSection
454
+ title={t("editPanel.sections.effects")}
455
+ actions={
456
+ <DropdownMenu>
457
+ <DropdownMenuTrigger asChild>
458
+ <Button
459
+ type="button"
460
+ variant="ghost"
461
+ size="icon"
462
+ className="size-6 cursor-pointer rounded-md text-muted-foreground hover:text-foreground"
463
+ aria-label={t("editPanel.labels.addLayer")}
464
+ >
465
+ <IconPlus className="size-3.5" />
466
+ </Button>
467
+ </DropdownMenuTrigger>
468
+ <DropdownMenuContent align="end" className="min-w-44">
469
+ <DropdownMenuItem
470
+ className="gap-2 !text-[11px]"
471
+ onSelect={addDropShadow}
472
+ >
473
+ <IconShadow className="size-3.5" />
474
+ {t("editPanel.labels.dropShadow")}
475
+ </DropdownMenuItem>
476
+ <DropdownMenuItem
477
+ className="gap-2 !text-[11px]"
478
+ onSelect={addLayerBlur}
479
+ >
480
+ <IconBlur className="size-3.5" />
481
+ {t("editPanel.labels.layerBlur")}
482
+ </DropdownMenuItem>
483
+ <DropdownMenuItem
484
+ className="gap-2 !text-[11px]"
485
+ onSelect={addBackgroundBlur}
486
+ >
487
+ <IconBackground className="size-3.5" />
488
+ {"Background blur" /* i18n-ignore design effect type */}
489
+ </DropdownMenuItem>
490
+ {glslShaderContext?.nodeId ? (
491
+ <DropdownMenuItem
492
+ className="gap-2 !text-[11px]"
493
+ onSelect={() => {
494
+ // Defer past the dropdown's close so the inline picker's
495
+ // focus handling isn't clobbered by menu teardown.
496
+ setTimeout(() => setShaderPickerOpen(true), 0);
497
+ }}
498
+ >
499
+ <IconWaveSine className="size-3.5" />
500
+ {t("editPanel.labels.shaderEffectType")}
501
+ </DropdownMenuItem>
502
+ ) : null}
503
+ </DropdownMenuContent>
504
+ </DropdownMenu>
505
+ }
506
+ >
507
+ {hasEffectsContent ? (
508
+ <>
509
+ {shadowLayers.length ? (
510
+ <div className="space-y-1.5">
511
+ {shadowLayers.map((layer, index) => (
512
+ <ShadowEffectRow
513
+ key={layer.id}
514
+ layer={layer}
515
+ index={index}
516
+ dragHandleLabel={t("editPanel.labels.reorderLayer")}
517
+ dropIndicator={
518
+ shadowDrag.dragIndex != null &&
519
+ shadowDrag.overIndex === index
520
+ ? shadowDrag.overIndex > shadowDrag.dragIndex
521
+ ? "after"
522
+ : "before"
523
+ : null
524
+ }
525
+ rowProps={shadowDrag.getRowProps(index)}
526
+ handleProps={shadowDrag.getHandleProps(index)}
527
+ onChange={(patch, meta) => {
528
+ const next = shadowLayers.map((candidate) =>
529
+ candidate.id === layer.id
530
+ ? { ...candidate, ...patch }
531
+ : candidate,
532
+ );
533
+ setShadowLayers(next, meta);
534
+ }}
535
+ onToggleVisibility={() => {
536
+ const visible = colorHasVisibleAlpha(layer.color);
537
+ const shadowStashKey = `${effectStashKey}:shadow:${layer.id}`;
538
+ if (visible) {
539
+ setHiddenEffectStash((prev) => ({
540
+ ...prev,
541
+ [shadowStashKey]: layer.color,
542
+ }));
543
+ const next = shadowLayers.map((candidate) =>
544
+ candidate.id === layer.id
545
+ ? {
546
+ ...candidate,
547
+ color: shadowColorWithOpacity(candidate.color, 0),
548
+ }
549
+ : candidate,
550
+ );
551
+ setShadowLayers(next);
552
+ return;
553
+ }
554
+
555
+ const restored =
556
+ hiddenEffectStash[shadowStashKey] ??
557
+ shadowColorWithOpacity(layer.color, 25);
558
+ setHiddenEffectStash((prev) => {
559
+ const next = { ...prev };
560
+ delete next[shadowStashKey];
561
+ return next;
562
+ });
563
+ const next = shadowLayers.map((candidate) =>
564
+ candidate.id === layer.id
565
+ ? { ...candidate, color: restored }
566
+ : candidate,
567
+ );
568
+ setShadowLayers(next);
569
+ }}
570
+ onRemove={() =>
571
+ setShadowLayers(
572
+ shadowLayers.filter(
573
+ (candidate) => candidate.id !== layer.id,
574
+ ),
575
+ )
576
+ }
577
+ element={element}
578
+ motionKeyframeContext={motionKeyframeContext}
579
+ />
580
+ ))}
581
+ </div>
582
+ ) : null}
583
+ {filterHasBlur ? (
584
+ /* design effect row for layer blur: flat row matching shadow rows */
585
+ <Popover>
586
+ <div className="group flex items-center gap-1.5">
587
+ <PopoverTrigger asChild>
588
+ <button
589
+ type="button"
590
+ className="flex h-6 min-w-0 flex-1 items-center gap-1.5 rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-left !text-[11px] hover:bg-[var(--design-editor-panel-raised-bg)]"
591
+ >
592
+ <span className="min-w-0 flex-1 truncate font-medium text-foreground">
593
+ {t("editPanel.labels.layerBlur")}
594
+ </span>
595
+ <span className="shrink-0 tabular-nums text-muted-foreground">
596
+ {Math.round(blurValue)}px
597
+ </span>
598
+ </button>
599
+ </PopoverTrigger>
600
+ <SectionIconButton
601
+ label={
602
+ blurValue > 0
603
+ ? t("editPanel.labels.hideLayer")
604
+ : t("editPanel.labels.showLayer")
605
+ }
606
+ onClick={() => {
607
+ if (blurValue > 0) {
608
+ setHiddenEffectStash((prev) => ({
609
+ ...prev,
610
+ [layerBlurStashKey]: String(blurValue),
611
+ }));
612
+ onStyleChange(
613
+ "filter",
614
+ setBlurFilterValue(styles.filter, 0),
615
+ );
616
+ return;
617
+ }
618
+
619
+ const restored = Number(
620
+ hiddenEffectStash[layerBlurStashKey],
621
+ );
622
+ const nextBlur =
623
+ Number.isFinite(restored) && restored > 0 ? restored : 4;
624
+ setHiddenEffectStash((prev) => {
625
+ const next = { ...prev };
626
+ delete next[layerBlurStashKey];
627
+ return next;
628
+ });
629
+ onStyleChange(
630
+ "filter",
631
+ setBlurFilterValue(styles.filter, nextBlur),
632
+ );
633
+ }}
634
+ >
635
+ {blurValue > 0 ? (
636
+ <IconEye className="size-3.5" />
637
+ ) : (
638
+ <IconEyeOff className="size-3.5" />
639
+ )}
640
+ </SectionIconButton>
641
+ <SectionIconButton
642
+ label={t("editPanel.labels.removeLayer")}
643
+ onClick={() => onStyleChange("filter", "none")}
644
+ disabled={!filterHasBlur}
645
+ >
646
+ <IconMinus className="size-3.5" />
647
+ </SectionIconButton>
648
+ </div>
649
+ <PopoverContent
650
+ side="left"
651
+ align="start"
652
+ sideOffset={8}
653
+ className="w-56 p-3"
654
+ >
655
+ <ScrubInput
656
+ label={t("editPanel.labels.blur")}
657
+ value={blurValue}
658
+ onChange={(value, meta) =>
659
+ onStyleChange(
660
+ "filter",
661
+ setBlurFilterValue(styles.filter, value),
662
+ meta,
663
+ )
664
+ }
665
+ unit="px"
666
+ min={0}
667
+ precision={1}
668
+ labelClassName="w-16"
669
+ inputClassName="h-6"
670
+ />
671
+ </PopoverContent>
672
+ </Popover>
673
+ ) : null}
674
+ {backdropFilterHasBlur ? (
675
+ /* M5 · Background (backdrop) blur effect row — mirrors the layer-blur row */
676
+ <Popover>
677
+ <div className="group flex items-center gap-1.5">
678
+ <PopoverTrigger asChild>
679
+ <button
680
+ type="button"
681
+ className="flex h-6 min-w-0 flex-1 items-center gap-1.5 rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-left !text-[11px] hover:bg-[var(--design-editor-panel-raised-bg)]"
682
+ >
683
+ <span className="min-w-0 flex-1 truncate font-medium text-foreground">
684
+ {"Background blur" /* i18n-ignore design effect type */}
685
+ </span>
686
+ <span className="shrink-0 tabular-nums text-muted-foreground">
687
+ {Math.round(backdropBlurValue)}px
688
+ </span>
689
+ </button>
690
+ </PopoverTrigger>
691
+ <SectionIconButton
692
+ label={
693
+ backdropBlurValue > 0
694
+ ? t("editPanel.labels.hideLayer")
695
+ : t("editPanel.labels.showLayer")
696
+ }
697
+ onClick={() => {
698
+ if (backdropBlurValue > 0) {
699
+ setHiddenEffectStash((prev) => ({
700
+ ...prev,
701
+ [backdropBlurStashKey]: String(backdropBlurValue),
702
+ }));
703
+ onStyleChange(
704
+ "backdropFilter",
705
+ setBlurFilterValue(backdropFilterValue, 0),
706
+ );
707
+ return;
708
+ }
709
+
710
+ const restored = Number(
711
+ hiddenEffectStash[backdropBlurStashKey],
712
+ );
713
+ const nextBlur =
714
+ Number.isFinite(restored) && restored > 0 ? restored : 8;
715
+ setHiddenEffectStash((prev) => {
716
+ const next = { ...prev };
717
+ delete next[backdropBlurStashKey];
718
+ return next;
719
+ });
720
+ onStyleChange(
721
+ "backdropFilter",
722
+ setBlurFilterValue(backdropFilterValue, nextBlur),
723
+ );
724
+ }}
725
+ >
726
+ {backdropBlurValue > 0 ? (
727
+ <IconEye className="size-3.5" />
728
+ ) : (
729
+ <IconEyeOff className="size-3.5" />
730
+ )}
731
+ </SectionIconButton>
732
+ <SectionIconButton
733
+ label={t("editPanel.labels.removeLayer")}
734
+ onClick={() => onStyleChange("backdropFilter", "none")}
735
+ disabled={!backdropFilterHasBlur}
736
+ >
737
+ <IconMinus className="size-3.5" />
738
+ </SectionIconButton>
739
+ </div>
740
+ <PopoverContent
741
+ side="left"
742
+ align="start"
743
+ sideOffset={8}
744
+ className="w-56 p-3"
745
+ >
746
+ <ScrubInput
747
+ label={t("editPanel.labels.blur")}
748
+ value={backdropBlurValue}
749
+ onChange={(value, meta) =>
750
+ onStyleChange(
751
+ "backdropFilter",
752
+ setBlurFilterValue(backdropFilterValue, value),
753
+ meta,
754
+ )
755
+ }
756
+ unit="px"
757
+ min={0}
758
+ precision={1}
759
+ labelClassName="w-16"
760
+ inputClassName="h-6"
761
+ />
762
+ </PopoverContent>
763
+ </Popover>
764
+ ) : null}
765
+ {glslShaderContext?.nodeId ? (
766
+ /* Code-backed GLSL shader effect — overlay canvas above the
767
+ element's content, persisted as editable GLSL in the screen HTML
768
+ (see shared/shader-fills.ts). Renders its row (when applied) and
769
+ the picker (when adding). */
770
+ <GlslShaderEffectSection
771
+ context={glslShaderContext}
772
+ pickerOpen={shaderPickerOpen}
773
+ onPickerOpenChange={setShaderPickerOpen}
774
+ />
775
+ ) : null}
776
+ </>
777
+ ) : null}
778
+ </PanelSection>
779
+ );
780
+ }