@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
@@ -2,14 +2,23 @@ import { useT } from "@agent-native/core/client";
2
2
  import {
3
3
  IconChevronDown,
4
4
  IconChevronRight,
5
+ IconClipboard,
6
+ IconCopy,
5
7
  IconEye,
6
8
  IconEyeOff,
9
+ IconFlipHorizontal,
10
+ IconFlipVertical,
11
+ IconFrame,
12
+ IconLayersSubtract,
13
+ IconLayersUnion,
7
14
  IconLock,
8
15
  IconLockOpen,
9
16
  IconLayoutGrid,
10
17
  IconPencil,
11
18
  IconPlus,
12
19
  IconSearch,
20
+ IconStackBack,
21
+ IconStackFront,
13
22
  } from "@tabler/icons-react";
14
23
  import {
15
24
  forwardRef,
@@ -35,6 +44,7 @@ import {
35
44
  ContextMenuContent,
36
45
  ContextMenuItem,
37
46
  ContextMenuSeparator,
47
+ ContextMenuShortcut,
38
48
  ContextMenuTrigger,
39
49
  } from "@/components/ui/context-menu";
40
50
  import { Input } from "@/components/ui/input";
@@ -146,6 +156,15 @@ export interface LayersPanelLabels {
146
156
  hide: string;
147
157
  show: string;
148
158
  rename: string;
159
+ copy: string;
160
+ pasteToReplace: string;
161
+ group: string;
162
+ ungroup: string;
163
+ frameSelection: string;
164
+ bringToFront: string;
165
+ sendToBack: string;
166
+ flipHorizontal: string;
167
+ flipVertical: string;
149
168
  }
150
169
 
151
170
  export interface LayersPanelProps {
@@ -181,6 +200,48 @@ export interface LayersPanelProps {
181
200
  // Board elements — top-level layer nodes projected from the board file.
182
201
  // When absent the panel is unchanged.
183
202
  boardElements?: LayersPanelNode[];
203
+ // Id of a layer currently hovered elsewhere (e.g. on the canvas). When set,
204
+ // the matching row gets a subtle hover-highlight background, visually
205
+ // distinct from selection. This is display-only: it never triggers the
206
+ // row's scroll-into-view behavior (that only follows selectedIds), and it
207
+ // never affects keyboard focus. Optional — the panel is unchanged when
208
+ // absent.
209
+ hoveredLayerId?: string | null;
210
+ // Figma-parity row context-menu actions beyond rename/lock/hide. Each item
211
+ // renders only when its callback prop is provided, so the panel keeps
212
+ // working correctly before every callback is wired up from the caller. See
213
+ // the LayerRow context menu below for the exact order/separators/shortcut
214
+ // hints — LIVE-VERIFIED against real Figma's layer-row menu: Copy, Paste
215
+ // to replace, Bring to front, Send to back, Group selection, Frame
216
+ // selection, Rename, Show/Hide, Lock/Unlock, Flip horizontal, Flip
217
+ // vertical. Real Figma has NO Duplicate/Delete/Paste-here on this menu
218
+ // (those are keyboard-only there), and NO Ungroup on a plain row — only on
219
+ // a container row (see onUngroupSelection below).
220
+ onCopyLayer?: (ids: string[]) => void;
221
+ // Kept for callers that still wire it (e.g. a future keyboard shortcut or
222
+ // a different surface); intentionally never rendered in the row menu
223
+ // itself, matching Figma (no "Paste here" on layer rows).
224
+ onPasteHere?: (targetId: string) => void;
225
+ onPasteToReplace?: (ids: string[]) => void;
226
+ // Kept for callers/back-compat; intentionally never rendered in the row
227
+ // menu itself, matching Figma (Duplicate is keyboard-only there).
228
+ onDuplicateLayer?: (ids: string[]) => void;
229
+ // Kept for callers/back-compat; intentionally never rendered in the row
230
+ // menu itself, matching Figma (Delete is keyboard-only there).
231
+ onDeleteLayer?: (ids: string[]) => void;
232
+ onGroupSelection?: (ids: string[]) => void;
233
+ onFrameSelection?: (ids: string[]) => void;
234
+ // Real Figma only offers Ungroup on a CONTAINER row (a group/frame you can
235
+ // ungroup), not on a plain leaf row. The row gates rendering this on
236
+ // `row.canAcceptChildren` (see showContextMenu/LayerRow below) in addition
237
+ // to this callback being provided.
238
+ onUngroupSelection?: (ids: string[]) => void;
239
+ onReorderLayer?: (
240
+ ids: string[],
241
+ direction: "front" | "forward" | "backward" | "back",
242
+ ) => void;
243
+ onFlipHorizontal?: (ids: string[]) => void;
244
+ onFlipVertical?: (ids: string[]) => void;
184
245
  }
185
246
 
186
247
  // L12: imperative handle so an external trigger (Cmd+R hotkey, canvas
@@ -260,6 +321,15 @@ function defaultLabels(t: ReturnType<typeof useT>): LayersPanelLabels {
260
321
  hide: t("layersPanel.hide"),
261
322
  show: t("layersPanel.show"),
262
323
  rename: t("layersPanel.rename"),
324
+ copy: t("layersPanel.copy"),
325
+ pasteToReplace: t("layersPanel.pasteToReplace"),
326
+ group: t("layersPanel.group"),
327
+ ungroup: t("layersPanel.ungroup"),
328
+ frameSelection: t("layersPanel.frameSelection"),
329
+ bringToFront: t("layersPanel.bringToFront"),
330
+ sendToBack: t("layersPanel.sendToBack"),
331
+ flipHorizontal: t("layersPanel.flipHorizontal"),
332
+ flipVertical: t("layersPanel.flipVertical"),
263
333
  };
264
334
  }
265
335
 
@@ -463,6 +533,40 @@ function nextExpandedIds(
463
533
  return Array.from(next);
464
534
  }
465
535
 
536
+ // Alt-click on a row's expand chevron (Figma behavior): expand/collapse the
537
+ // node AND every descendant that can itself have children, in one batched
538
+ // state change. Pure tree walk — collects every node id with a non-empty
539
+ // children array so nextExpandedIdsForSubtree can add/remove them all at
540
+ // once instead of the caller looping many onExpandedIdsChange calls.
541
+ export function collectDescendantContainerIds(node: LayersPanelNode): string[] {
542
+ const ids: string[] = [];
543
+ function visit(current: LayersPanelNode) {
544
+ const children = current.children ?? [];
545
+ if (children.length === 0) return;
546
+ ids.push(current.id);
547
+ children.forEach(visit);
548
+ }
549
+ visit(node);
550
+ return ids;
551
+ }
552
+
553
+ export function nextExpandedIdsForSubtree(
554
+ ids: readonly string[],
555
+ node: LayersPanelNode,
556
+ expanded: boolean,
557
+ ): string[] {
558
+ const subtreeIds = collectDescendantContainerIds(node);
559
+ const next = new Set(ids);
560
+ subtreeIds.forEach((id) => {
561
+ if (expanded) {
562
+ next.add(id);
563
+ } else {
564
+ next.delete(id);
565
+ }
566
+ });
567
+ return Array.from(next);
568
+ }
569
+
466
570
  /**
467
571
  * L1: pure computation for the auto-expand-ancestors-of-selection effect.
468
572
  * Given the current selection's ancestor ids and the CURRENT expanded set,
@@ -636,6 +740,23 @@ export function getDraggedLayerIdsForRows(args: {
636
740
  });
637
741
  }
638
742
 
743
+ // Row context-menu actions (copy/duplicate/delete/group/reorder) operate on
744
+ // the whole current selection when the right-clicked row is already part of
745
+ // it (matching Figma), or on just that row otherwise (right-clicking an
746
+ // unselected layer acts on that layer alone). Mirrors the same shape as
747
+ // getDraggedLayerIdsForRows's selection-vs-single-row resolution, kept
748
+ // separate since context-menu actions don't need the descendant-exclusion
749
+ // step a drag payload does.
750
+ export function getContextMenuTargetIds(args: {
751
+ selectedIds: readonly string[];
752
+ nodeId: string;
753
+ visibleRows: readonly FlatLayerRow[];
754
+ }): string[] {
755
+ return args.selectedIds.includes(args.nodeId)
756
+ ? getTreeOrderedLayerIds(args.selectedIds, args.visibleRows)
757
+ : [args.nodeId];
758
+ }
759
+
639
760
  export function shouldResyncLayerSelectionAnchor(args: {
640
761
  selectionSignature: string;
641
762
  lastPanelSelectionSignature: string;
@@ -700,6 +821,18 @@ function LayersPanelImpl(
700
821
  onMoveLayer,
701
822
  canMoveLayer,
702
823
  boardElements,
824
+ hoveredLayerId,
825
+ onCopyLayer,
826
+ onPasteHere,
827
+ onPasteToReplace,
828
+ onDuplicateLayer,
829
+ onDeleteLayer,
830
+ onGroupSelection,
831
+ onFrameSelection,
832
+ onUngroupSelection,
833
+ onReorderLayer,
834
+ onFlipHorizontal,
835
+ onFlipVertical,
703
836
  }: LayersPanelProps,
704
837
  ref: Ref<LayersPanelHandle>,
705
838
  ) {
@@ -1013,7 +1146,17 @@ function LayersPanelImpl(
1013
1146
  // render — this keeps LayerRow's props referentially stable so
1014
1147
  // React.memo(LayerRow) actually skips re-renders.
1015
1148
  const handleToggleExpanded = useCallback(
1016
- (id: string, expanded: boolean) => {
1149
+ (id: string, expanded: boolean, node?: LayersPanelNode) => {
1150
+ // Alt-click (see LayerRow's chevron onClick): expand/collapse this node
1151
+ // AND all of its descendants in one batched state change, matching
1152
+ // Figma. Only takes this path when the caller passes the node (the
1153
+ // plain toggle path below stays a single-id update).
1154
+ if (node) {
1155
+ onExpandedIdsChange(
1156
+ nextExpandedIdsForSubtree(expandedIdsRef.current, node, expanded),
1157
+ );
1158
+ return;
1159
+ }
1017
1160
  onExpandedIdsChange(
1018
1161
  nextExpandedIds(expandedIdsRef.current, id, expanded),
1019
1162
  );
@@ -1254,6 +1397,8 @@ function LayersPanelImpl(
1254
1397
  const isInSelectedSubtree = row.ancestorIds.some((id) =>
1255
1398
  selectedIdSet.has(id),
1256
1399
  );
1400
+ const isHovered =
1401
+ hoveredLayerId != null && row.node.id === hoveredLayerId;
1257
1402
  const isActiveScreen =
1258
1403
  row.node.id === activeScreenId &&
1259
1404
  (row.node.type === "file" ||
@@ -1273,6 +1418,7 @@ function LayersPanelImpl(
1273
1418
  isSelected={isSelected}
1274
1419
  isInSelectedSubtree={isInSelectedSubtree}
1275
1420
  isActiveScreen={isActiveScreen}
1421
+ isHovered={isHovered}
1276
1422
  isRenaming={isRenaming}
1277
1423
  renameDraft={isRenaming ? renameDraft : ""}
1278
1424
  registerRowElement={registerRowElement}
@@ -1294,6 +1440,14 @@ function LayersPanelImpl(
1294
1440
  selectedIdsRef={selectedIdsRef}
1295
1441
  visibleRowsRef={visibleRowsRef}
1296
1442
  rootsRef={rootsRef}
1443
+ onCopyLayer={onCopyLayer}
1444
+ onPasteToReplace={onPasteToReplace}
1445
+ onGroupSelection={onGroupSelection}
1446
+ onFrameSelection={onFrameSelection}
1447
+ onUngroupSelection={onUngroupSelection}
1448
+ onReorderLayer={onReorderLayer}
1449
+ onFlipHorizontal={onFlipHorizontal}
1450
+ onFlipVertical={onFlipVertical}
1297
1451
  />
1298
1452
  );
1299
1453
  })}
@@ -1325,6 +1479,10 @@ interface LayerRowProps {
1325
1479
  isSelected: boolean;
1326
1480
  isInSelectedSubtree: boolean;
1327
1481
  isActiveScreen: boolean;
1482
+ // Display-only hover highlight (e.g. mirroring canvas hover), distinct from
1483
+ // selection. Never drives scroll-into-view or focus — see hoveredLayerId on
1484
+ // LayersPanelProps.
1485
+ isHovered: boolean;
1328
1486
  isRenaming: boolean;
1329
1487
  registerRowElement: (rowKey: string, element: HTMLDivElement | null) => void;
1330
1488
  renameDraft: string;
@@ -1342,7 +1500,13 @@ interface LayerRowProps {
1342
1500
  source: "keyboard" | "pointer";
1343
1501
  },
1344
1502
  ) => void;
1345
- onToggleExpanded: (id: string, expanded: boolean) => void;
1503
+ // node is optional; passed on alt-click so the caller can batch-expand the
1504
+ // whole subtree in one state change instead of a single-id toggle.
1505
+ onToggleExpanded: (
1506
+ id: string,
1507
+ expanded: boolean,
1508
+ node?: LayersPanelNode,
1509
+ ) => void;
1346
1510
  onToggleLocked?: (id: string, locked: boolean) => void;
1347
1511
  onToggleHidden?: (id: string, hidden: boolean) => void;
1348
1512
  onHoverLayer?: (id: string) => void;
@@ -1363,6 +1527,23 @@ interface LayerRowProps {
1363
1527
  // full-tree ancestor map — see buildAncestorIdMap and L13 in the drag-start
1364
1528
  // handler below.
1365
1529
  rootsRef: RefObject<LayersPanelNode[]>;
1530
+ // Figma-parity context-menu actions. Each is optional; the corresponding
1531
+ // menu item only renders when its callback is provided (see showContextMenu
1532
+ // / the ContextMenuContent below). Note: onPasteHere/onDuplicateLayer/
1533
+ // onDeleteLayer are NOT threaded down to the row — real Figma's layer-row
1534
+ // menu has no Paste here/Duplicate/Delete items (see LayersPanelProps for
1535
+ // the full back-compat callback surface).
1536
+ onCopyLayer?: (ids: string[]) => void;
1537
+ onPasteToReplace?: (ids: string[]) => void;
1538
+ onGroupSelection?: (ids: string[]) => void;
1539
+ onFrameSelection?: (ids: string[]) => void;
1540
+ onUngroupSelection?: (ids: string[]) => void;
1541
+ onReorderLayer?: (
1542
+ ids: string[],
1543
+ direction: "front" | "forward" | "backward" | "back",
1544
+ ) => void;
1545
+ onFlipHorizontal?: (ids: string[]) => void;
1546
+ onFlipVertical?: (ids: string[]) => void;
1366
1547
  }
1367
1548
 
1368
1549
  const LayerRow = memo(function LayerRow({
@@ -1372,6 +1553,7 @@ const LayerRow = memo(function LayerRow({
1372
1553
  isSelected,
1373
1554
  isInSelectedSubtree,
1374
1555
  isActiveScreen,
1556
+ isHovered,
1375
1557
  isRenaming,
1376
1558
  registerRowElement,
1377
1559
  renameDraft,
@@ -1393,6 +1575,14 @@ const LayerRow = memo(function LayerRow({
1393
1575
  selectedIdsRef,
1394
1576
  visibleRowsRef,
1395
1577
  rootsRef,
1578
+ onCopyLayer,
1579
+ onPasteToReplace,
1580
+ onGroupSelection,
1581
+ onFrameSelection,
1582
+ onUngroupSelection,
1583
+ onReorderLayer,
1584
+ onFlipHorizontal,
1585
+ onFlipVertical,
1396
1586
  }: LayerRowProps) {
1397
1587
  const { node, depth, hasChildren, canAcceptChildren } = row;
1398
1588
  const isComponentLayer = layerNodeIsComponent(node);
@@ -1470,36 +1660,19 @@ const LayerRow = memo(function LayerRow({
1470
1660
  });
1471
1661
  };
1472
1662
 
1663
+ // GROUND TRUTH (live-verified against real Figma): after clicking a layer
1664
+ // row, ArrowUp/ArrowDown/Home/End do NOT navigate the layers list and must
1665
+ // NOT be intercepted here at all — no preventDefault, no focus move, no
1666
+ // selection change. Figma's list focus does not consume those keys; they
1667
+ // fall through to the app's global hotkey nudge handler, which moves the
1668
+ // SELECTED OBJECT on canvas by 1px (arrow) or listens for its own Home/End
1669
+ // handling. A previous revision made this row intercept those keys (first
1670
+ // to move DOM focus, later to change selection directly) — both were wrong
1671
+ // and are removed here. ArrowLeft/ArrowRight are the one exception Figma
1672
+ // keeps at the list level: they only toggle the focused row's own
1673
+ // expand/collapse (chevron) state, and must NOT change selection while
1674
+ // doing so.
1473
1675
  const handleKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {
1474
- const getFocusableButtons = () => {
1475
- const tree = event.currentTarget.closest('[role="tree"]');
1476
- if (!tree) return [];
1477
- return Array.from(
1478
- tree.querySelectorAll<HTMLButtonElement>("[data-layer-row-button]"),
1479
- ).filter((button) => !button.disabled);
1480
- };
1481
- const focusVisibleButton = (nextIndex: number) => {
1482
- const buttons = getFocusableButtons();
1483
- const target =
1484
- buttons[Math.max(0, Math.min(buttons.length - 1, nextIndex))];
1485
- target?.focus();
1486
- return Boolean(target);
1487
- };
1488
- const focusNodeButton = (nodeId: string) => {
1489
- const tree = event.currentTarget.closest('[role="tree"]');
1490
- const target = tree?.querySelector<HTMLButtonElement>(
1491
- `[data-layer-node-id="${CSS.escape(nodeId)}"]`,
1492
- );
1493
- if (!target || target.disabled) return false;
1494
- target.focus();
1495
- return true;
1496
- };
1497
- const currentIndex = visibleRowsRef.current.findIndex(
1498
- (visibleRow) => visibleRow.rowKey === row.rowKey,
1499
- );
1500
- const focusableButtons = getFocusableButtons();
1501
- const currentFocusableIndex = focusableButtons.indexOf(event.currentTarget);
1502
-
1503
1676
  if (event.key === "Enter" || event.key === " " || event.key === "Space") {
1504
1677
  event.preventDefault();
1505
1678
  if (!selectable) return;
@@ -1516,55 +1689,16 @@ const LayerRow = memo(function LayerRow({
1516
1689
  onStartRename(node);
1517
1690
  return;
1518
1691
  }
1519
- if (event.key === "ArrowDown") {
1520
- event.preventDefault();
1521
- focusVisibleButton(currentFocusableIndex + 1);
1522
- return;
1523
- }
1524
- if (event.key === "ArrowUp") {
1525
- event.preventDefault();
1526
- focusVisibleButton(currentFocusableIndex - 1);
1527
- return;
1528
- }
1529
- if (event.key === "Home") {
1530
- event.preventDefault();
1531
- focusVisibleButton(0);
1532
- return;
1533
- }
1534
- if (event.key === "End") {
1535
- event.preventDefault();
1536
- focusVisibleButton(focusableButtons.length - 1);
1537
- return;
1538
- }
1539
1692
  if (event.key === "ArrowRight" && hasChildren && !isExpanded) {
1540
1693
  event.preventDefault();
1541
1694
  onToggleExpanded(node.id, true);
1542
1695
  return;
1543
1696
  }
1544
- if (event.key === "ArrowRight" && hasChildren && isExpanded) {
1545
- event.preventDefault();
1546
- const nextButton = focusableButtons[currentFocusableIndex + 1];
1547
- const childId = nextButton?.dataset.layerNodeId;
1548
- const child = childId
1549
- ? visibleRowsRef.current.find(
1550
- (visibleRow) => visibleRow.node.id === childId,
1551
- )
1552
- : null;
1553
- if (child?.ancestorIds.includes(node.id)) {
1554
- focusVisibleButton(currentFocusableIndex + 1);
1555
- }
1556
- return;
1557
- }
1558
1697
  if (event.key === "ArrowLeft" && hasChildren && isExpanded) {
1559
1698
  event.preventDefault();
1560
1699
  onToggleExpanded(node.id, false);
1561
1700
  return;
1562
1701
  }
1563
- if (event.key === "ArrowLeft" && row.ancestorIds.length > 0) {
1564
- event.preventDefault();
1565
- const parentId = row.ancestorIds[row.ancestorIds.length - 1];
1566
- if (parentId) focusNodeButton(parentId);
1567
- }
1568
1702
  };
1569
1703
 
1570
1704
  const handleDragStart = (event: DragEvent<HTMLDivElement>) => {
@@ -1724,9 +1858,44 @@ const LayerRow = memo(function LayerRow({
1724
1858
  clearSpringLoadTimer();
1725
1859
  };
1726
1860
 
1861
+ // Right-clicking a row that's already part of the current selection acts on
1862
+ // the whole selection (matching Figma); right-clicking an unselected row
1863
+ // acts on just that row. Computed lazily at action time (not memoized on
1864
+ // render) so it always reflects the live selectedIdsRef/visibleRowsRef —
1865
+ // both are refs updated outside the render cycle (see their declarations in
1866
+ // LayersPanel), so a render-time useMemo could see stale values by the time
1867
+ // the user actually picks a menu item.
1868
+ const getContextMenuTargetIdsForRow = () =>
1869
+ getContextMenuTargetIds({
1870
+ selectedIds: selectedIdsRef.current,
1871
+ nodeId: node.id,
1872
+ visibleRows: visibleRowsRef.current,
1873
+ });
1874
+
1875
+ // Real Figma only offers Ungroup on a container row (something you could
1876
+ // actually ungroup), never on a plain leaf row — gate it on
1877
+ // canAcceptChildren in addition to the callback being provided. Duplicate/
1878
+ // Delete/Paste-here are intentionally excluded here: real Figma's layer
1879
+ // row menu doesn't have them (they're keyboard-only there), so they must
1880
+ // not factor into whether the menu/trigger renders at all.
1881
+ const canUngroupThisRow = Boolean(onUngroupSelection && canAcceptChildren);
1882
+ const hasEditActions = Boolean(
1883
+ onCopyLayer ||
1884
+ onPasteToReplace ||
1885
+ onGroupSelection ||
1886
+ onFrameSelection ||
1887
+ canUngroupThisRow ||
1888
+ onReorderLayer ||
1889
+ onFlipHorizontal ||
1890
+ onFlipVertical,
1891
+ );
1892
+
1727
1893
  const showContextMenu =
1728
1894
  selectable &&
1729
- (Boolean(onRename && node.renamable !== false) || lockable || hideable);
1895
+ (Boolean(onRename && node.renamable !== false) ||
1896
+ lockable ||
1897
+ hideable ||
1898
+ hasEditActions);
1730
1899
 
1731
1900
  return (
1732
1901
  <ContextMenu>
@@ -1764,10 +1933,16 @@ const LayerRow = memo(function LayerRow({
1764
1933
  onMouseLeave={() => onLeaveLayer?.(node.id)}
1765
1934
  >
1766
1935
  {activeDrop === "before" ? (
1767
- <span className="pointer-events-none absolute left-2 right-2 top-0 z-10 h-px bg-[var(--design-editor-accent-color)]" />
1936
+ <span
1937
+ className="pointer-events-none absolute right-2 top-0 z-10 h-px bg-[var(--design-editor-accent-color)]"
1938
+ style={{ left: rowIndent(depth) }}
1939
+ />
1768
1940
  ) : null}
1769
1941
  {activeDrop === "after" ? (
1770
- <span className="pointer-events-none absolute bottom-0 left-2 right-2 z-10 h-px bg-[var(--design-editor-accent-color)]" />
1942
+ <span
1943
+ className="pointer-events-none absolute bottom-0 right-2 z-10 h-px bg-[var(--design-editor-accent-color)]"
1944
+ style={{ left: rowIndent(depth) }}
1945
+ />
1771
1946
  ) : null}
1772
1947
  <div
1773
1948
  className={cn(
@@ -1790,6 +1965,15 @@ const LayerRow = memo(function LayerRow({
1790
1965
  !isInSelectedSubtree &&
1791
1966
  !isActiveScreen &&
1792
1967
  "text-foreground/90 hover:bg-[var(--design-editor-layer-hover-color)] hover:text-foreground",
1968
+ // Canvas-hover highlight (hoveredLayerId): a subtle background,
1969
+ // visually distinct from selection/subtree/active-screen state.
1970
+ // Only applied when none of those stronger states already own
1971
+ // the row's background, and never triggers scroll-into-view.
1972
+ isHovered &&
1973
+ !isSelected &&
1974
+ !isInSelectedSubtree &&
1975
+ !isActiveScreen &&
1976
+ "bg-[var(--design-editor-layer-hover-color)] text-foreground",
1793
1977
  node.hidden && "text-muted-foreground",
1794
1978
  )}
1795
1979
  style={{ paddingLeft: rowIndent(depth) }}
@@ -1803,7 +1987,13 @@ const LayerRow = memo(function LayerRow({
1803
1987
  aria-label={isExpanded ? labels.collapse : labels.expand}
1804
1988
  onClick={(event) => {
1805
1989
  event.stopPropagation();
1806
- onToggleExpanded(node.id, !isExpanded);
1990
+ // Alt-click (Figma behavior): expand/collapse this node AND
1991
+ // all of its descendants in one batched update.
1992
+ onToggleExpanded(
1993
+ node.id,
1994
+ !isExpanded,
1995
+ event.altKey ? node : undefined,
1996
+ );
1807
1997
  }}
1808
1998
  >
1809
1999
  {isExpanded ? (
@@ -1886,7 +2076,6 @@ const LayerRow = memo(function LayerRow({
1886
2076
  "min-w-0 flex-1 truncate font-medium leading-none",
1887
2077
  isComponentLayer &&
1888
2078
  "text-[var(--design-editor-component-color)]",
1889
- node.hidden && "line-through",
1890
2079
  )}
1891
2080
  title={node.name}
1892
2081
  >
@@ -1968,7 +2157,98 @@ const LayerRow = memo(function LayerRow({
1968
2157
  </div>
1969
2158
  </ContextMenuTrigger>
1970
2159
  {showContextMenu ? (
1971
- <ContextMenuContent className="z-[300] min-w-[160px] text-[12px]">
2160
+ <ContextMenuContent className="z-[300] min-w-[200px] text-[12px]">
2161
+ {/* LIVE-VERIFIED Figma layer-row menu order: Copy, Paste to
2162
+ replace — Bring to front, Send to back — Group selection,
2163
+ (Ungroup, container rows only), Frame selection, Rename —
2164
+ Show/Hide, Lock/Unlock — Flip horizontal, Flip vertical. Real
2165
+ Figma has no Duplicate/Delete/Paste-here on this menu (those
2166
+ are keyboard-only there — see ⌘D/Delete). Each item only
2167
+ renders when its callback prop is provided, so the menu
2168
+ degrades gracefully before every callback is wired up from the
2169
+ caller. */}
2170
+ {onCopyLayer ? (
2171
+ <ContextMenuItem
2172
+ className="gap-2 text-[12px]"
2173
+ onSelect={() => onCopyLayer(getContextMenuTargetIdsForRow())}
2174
+ >
2175
+ <IconCopy className="size-3.5 text-muted-foreground" />
2176
+ {labels.copy}
2177
+ <ContextMenuShortcut>⌘C</ContextMenuShortcut>
2178
+ </ContextMenuItem>
2179
+ ) : null}
2180
+ {onPasteToReplace ? (
2181
+ <ContextMenuItem
2182
+ className="gap-2 text-[12px]"
2183
+ onSelect={() => onPasteToReplace(getContextMenuTargetIdsForRow())}
2184
+ >
2185
+ <IconClipboard className="size-3.5 text-muted-foreground" />
2186
+ {labels.pasteToReplace}
2187
+ <ContextMenuShortcut>
2188
+ {"⇧⌘R" /* i18n-ignore keyboard shortcut glyph */}
2189
+ </ContextMenuShortcut>
2190
+ </ContextMenuItem>
2191
+ ) : null}
2192
+
2193
+ {(onCopyLayer || onPasteToReplace) && onReorderLayer ? (
2194
+ <ContextMenuSeparator />
2195
+ ) : null}
2196
+
2197
+ {onReorderLayer ? (
2198
+ <>
2199
+ <ContextMenuItem
2200
+ className="gap-2 text-[12px]"
2201
+ onSelect={() =>
2202
+ onReorderLayer(getContextMenuTargetIdsForRow(), "front")
2203
+ }
2204
+ >
2205
+ <IconStackFront className="size-3.5 text-muted-foreground" />
2206
+ {labels.bringToFront}
2207
+ <ContextMenuShortcut>]</ContextMenuShortcut>
2208
+ </ContextMenuItem>
2209
+ <ContextMenuItem
2210
+ className="gap-2 text-[12px]"
2211
+ onSelect={() =>
2212
+ onReorderLayer(getContextMenuTargetIdsForRow(), "back")
2213
+ }
2214
+ >
2215
+ <IconStackBack className="size-3.5 text-muted-foreground" />
2216
+ {labels.sendToBack}
2217
+ <ContextMenuShortcut>[</ContextMenuShortcut>
2218
+ </ContextMenuItem>
2219
+ </>
2220
+ ) : null}
2221
+
2222
+ {onReorderLayer &&
2223
+ (onGroupSelection ||
2224
+ canUngroupThisRow ||
2225
+ onFrameSelection ||
2226
+ onRename) ? (
2227
+ <ContextMenuSeparator />
2228
+ ) : null}
2229
+
2230
+ {onGroupSelection ? (
2231
+ <ContextMenuItem
2232
+ className="gap-2 text-[12px]"
2233
+ onSelect={() => onGroupSelection(getContextMenuTargetIdsForRow())}
2234
+ >
2235
+ <IconLayersUnion className="size-3.5 text-muted-foreground" />
2236
+ {labels.group}
2237
+ <ContextMenuShortcut>⌘G</ContextMenuShortcut>
2238
+ </ContextMenuItem>
2239
+ ) : null}
2240
+ {onFrameSelection ? (
2241
+ <ContextMenuItem
2242
+ className="gap-2 text-[12px]"
2243
+ onSelect={() => onFrameSelection(getContextMenuTargetIdsForRow())}
2244
+ >
2245
+ <IconFrame className="size-3.5 text-muted-foreground" />
2246
+ {labels.frameSelection}
2247
+ <ContextMenuShortcut>
2248
+ {"⌥⌘G" /* i18n-ignore keyboard shortcut glyph */}
2249
+ </ContextMenuShortcut>
2250
+ </ContextMenuItem>
2251
+ ) : null}
1972
2252
  {onRename && node.renamable !== false ? (
1973
2253
  <ContextMenuItem
1974
2254
  className="gap-2 text-[12px]"
@@ -1976,11 +2256,50 @@ const LayerRow = memo(function LayerRow({
1976
2256
  >
1977
2257
  <IconPencil className="size-3.5 text-muted-foreground" />
1978
2258
  {labels.rename}
2259
+ <ContextMenuShortcut>⌘R</ContextMenuShortcut>
1979
2260
  </ContextMenuItem>
1980
2261
  ) : null}
1981
- {onRename && node.renamable !== false && (lockable || hideable) ? (
2262
+
2263
+ {/* Real Figma only shows Ungroup on a container row — a plain row
2264
+ never gets it, even when the callback is wired up. */}
2265
+ {canUngroupThisRow ? (
2266
+ <ContextMenuItem
2267
+ className="gap-2 text-[12px]"
2268
+ onSelect={() =>
2269
+ onUngroupSelection?.(getContextMenuTargetIdsForRow())
2270
+ }
2271
+ >
2272
+ <IconLayersSubtract className="size-3.5 text-muted-foreground" />
2273
+ {labels.ungroup}
2274
+ <ContextMenuShortcut>
2275
+ {"⇧⌘G" /* i18n-ignore keyboard shortcut glyph */}
2276
+ </ContextMenuShortcut>
2277
+ </ContextMenuItem>
2278
+ ) : null}
2279
+
2280
+ {(onGroupSelection ||
2281
+ canUngroupThisRow ||
2282
+ onFrameSelection ||
2283
+ (onRename && node.renamable !== false)) &&
2284
+ (lockable || hideable) ? (
1982
2285
  <ContextMenuSeparator />
1983
2286
  ) : null}
2287
+ {hideable ? (
2288
+ <ContextMenuItem
2289
+ className="gap-2 text-[12px]"
2290
+ onSelect={() => onToggleHidden?.(node.id, !node.hidden)}
2291
+ >
2292
+ {node.hidden ? (
2293
+ <IconEye className="size-3.5 text-muted-foreground" />
2294
+ ) : (
2295
+ <IconEyeOff className="size-3.5 text-muted-foreground" />
2296
+ )}
2297
+ {node.hidden ? labels.show : labels.hide}
2298
+ <ContextMenuShortcut>
2299
+ {"⇧⌘H" /* i18n-ignore keyboard shortcut glyph */}
2300
+ </ContextMenuShortcut>
2301
+ </ContextMenuItem>
2302
+ ) : null}
1984
2303
  {lockable ? (
1985
2304
  <ContextMenuItem
1986
2305
  className="gap-2 text-[12px]"
@@ -1992,19 +2311,34 @@ const LayerRow = memo(function LayerRow({
1992
2311
  <IconLock className="size-3.5 text-muted-foreground" />
1993
2312
  )}
1994
2313
  {node.locked ? labels.unlock : labels.lock}
2314
+ <ContextMenuShortcut>
2315
+ {"⇧⌘L" /* i18n-ignore keyboard shortcut glyph */}
2316
+ </ContextMenuShortcut>
1995
2317
  </ContextMenuItem>
1996
2318
  ) : null}
1997
- {hideable ? (
2319
+
2320
+ {(lockable || hideable) && (onFlipHorizontal || onFlipVertical) ? (
2321
+ <ContextMenuSeparator />
2322
+ ) : null}
2323
+
2324
+ {onFlipHorizontal ? (
1998
2325
  <ContextMenuItem
1999
2326
  className="gap-2 text-[12px]"
2000
- onSelect={() => onToggleHidden?.(node.id, !node.hidden)}
2327
+ onSelect={() => onFlipHorizontal(getContextMenuTargetIdsForRow())}
2001
2328
  >
2002
- {node.hidden ? (
2003
- <IconEye className="size-3.5 text-muted-foreground" />
2004
- ) : (
2005
- <IconEyeOff className="size-3.5 text-muted-foreground" />
2006
- )}
2007
- {node.hidden ? labels.show : labels.hide}
2329
+ <IconFlipHorizontal className="size-3.5 text-muted-foreground" />
2330
+ {labels.flipHorizontal}
2331
+ <ContextMenuShortcut>⇧H</ContextMenuShortcut>
2332
+ </ContextMenuItem>
2333
+ ) : null}
2334
+ {onFlipVertical ? (
2335
+ <ContextMenuItem
2336
+ className="gap-2 text-[12px]"
2337
+ onSelect={() => onFlipVertical(getContextMenuTargetIdsForRow())}
2338
+ >
2339
+ <IconFlipVertical className="size-3.5 text-muted-foreground" />
2340
+ {labels.flipVertical}
2341
+ <ContextMenuShortcut>⇧V</ContextMenuShortcut>
2008
2342
  </ContextMenuItem>
2009
2343
  ) : null}
2010
2344
  </ContextMenuContent>