@agent-native/core 0.80.7 → 0.80.9

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 (302) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +18 -0
  3. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +6 -0
  4. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +8 -0
  5. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +10 -2
  6. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +10 -2
  7. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +7 -0
  8. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +7 -0
  9. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +7 -0
  10. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +9 -2
  11. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +6 -0
  12. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +6 -0
  13. package/corpus/core/docs/content/template-design.mdx +9 -2
  14. package/corpus/core/package.json +1 -1
  15. package/corpus/core/src/cli/mcp.ts +10 -0
  16. package/corpus/core/src/cli/skills.ts +58 -12
  17. package/corpus/core/src/client/AssistantChat.tsx +70 -27
  18. package/corpus/core/src/client/analytics.ts +3 -1
  19. package/corpus/core/src/client/error-format.ts +25 -0
  20. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +13 -11
  21. package/corpus/core/src/client/guided-questions.tsx +32 -4
  22. package/corpus/core/src/client/onboarding/OnboardingPanel.tsx +33 -1
  23. package/corpus/core/src/client/resources/BuiltinCapabilityDetail.tsx +8 -0
  24. package/corpus/core/src/client/resources/use-builtin-capabilities.ts +4 -2
  25. package/corpus/core/src/client/session-replay.ts +34 -0
  26. package/corpus/core/src/client/sharing/ShareButton.tsx +106 -21
  27. package/corpus/core/src/coding-tools/index.ts +1 -1
  28. package/corpus/core/src/mcp/index.ts +2 -0
  29. package/corpus/core/src/mcp/screen-memory-stdio.ts +290 -0
  30. package/corpus/core/src/mcp-client/builtin-capabilities.ts +13 -1
  31. package/corpus/core/src/mcp-client/index.ts +9 -0
  32. package/corpus/core/src/mcp-client/screen-memory-local.ts +461 -0
  33. package/corpus/core/src/onboarding/types.ts +7 -0
  34. package/corpus/core/src/server/agent-chat-plugin.ts +25 -0
  35. package/corpus/core/src/server/agents-bundle.ts +35 -7
  36. package/corpus/core/src/styles/agent-native.css +5 -0
  37. package/corpus/core/src/templates/default/app/global.css +35 -35
  38. package/corpus/templates/analytics/AGENTS.md +6 -0
  39. package/corpus/templates/analytics/actions/github-repo-files.ts +9 -0
  40. package/corpus/templates/analytics/app/components/layout/Layout.tsx +4 -4
  41. package/corpus/templates/analytics/app/global.css +144 -40
  42. package/corpus/templates/analytics/app/pages/adhoc/explorer/index.tsx +1 -1
  43. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +1 -1
  44. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +1 -1
  45. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +4 -4
  46. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +9 -9
  47. package/corpus/templates/analytics/changelog/2026-06-29-agents-can-search-and-read-connected-github-repositories-whe.md +6 -0
  48. package/corpus/templates/analytics/changelog/2026-06-29-sessions-and-explorer-layouts-adapt-when-the-agent-sidebar-is.md +6 -0
  49. package/corpus/templates/assets/app/components/layout/Layout.tsx +3 -1
  50. package/corpus/templates/assets/app/components/library/LibraryPresetGrid.tsx +1 -1
  51. package/corpus/templates/assets/app/global.css +99 -35
  52. package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
  53. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +4 -4
  54. package/corpus/templates/assets/app/routes/library.tsx +4 -4
  55. package/corpus/templates/assets/changelog/2026-06-29-asset-library-and-detail-layouts-adapt-when-the-agent-sidebar.md +6 -0
  56. package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -1
  57. package/corpus/templates/brain/app/global.css +63 -35
  58. package/corpus/templates/brain/app/routes/search.tsx +1 -1
  59. package/corpus/templates/brain/app/routes/settings.tsx +1 -1
  60. package/corpus/templates/brain/app/routes/sources.tsx +2 -2
  61. package/corpus/templates/brain/changelog/2026-06-29-search-settings-and-source-layouts-adapt-when-the-agent-sidebar.md +6 -0
  62. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +4 -4
  63. package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +3 -2
  64. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +4 -2
  65. package/corpus/templates/calendar/app/global.css +69 -35
  66. package/corpus/templates/calendar/changelog/2026-06-29-event-and-find-time-layouts-adapt-when-the-agent-sidebar-is.md +6 -0
  67. package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -1
  68. package/corpus/templates/chat/app/global.css +35 -35
  69. package/corpus/templates/chat/changelog/2026-06-29-chat-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  70. package/corpus/templates/clips/AGENTS.md +13 -0
  71. package/corpus/templates/clips/actions/get-screen-memory-status.ts +12 -0
  72. package/corpus/templates/clips/actions/query-screen-memory-context.ts +29 -0
  73. package/corpus/templates/clips/actions/view-screen.ts +2 -2
  74. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  75. package/corpus/templates/clips/app/global.css +47 -35
  76. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +3 -3
  77. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +45 -18
  78. package/corpus/templates/clips/changelog/2026-06-29-meeting-detail-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-06-29-screen-memory-keeps-a-local-rolling-screen-buffer-for-agent-context.md +5 -0
  80. package/corpus/templates/clips/changelog/2026-06-29-video-chat-is-easier-to-find-on-recording-pages-and-s3-setup.md +6 -0
  81. package/corpus/templates/clips/chrome-extension/src/styles.css +39 -39
  82. package/corpus/templates/clips/desktop/src/app.tsx +290 -1
  83. package/corpus/templates/clips/desktop/src/shared/config.ts +83 -0
  84. package/corpus/templates/clips/desktop/src/styles.css +67 -52
  85. package/corpus/templates/clips/desktop/src-tauri/src/config.rs +50 -0
  86. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +17 -0
  87. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +13 -13
  88. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +990 -0
  89. package/corpus/templates/clips/server/plugins/onboarding.ts +2 -0
  90. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +69 -16
  91. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +3 -3
  92. package/corpus/templates/content/app/components/layout/Layout.tsx +1 -1
  93. package/corpus/templates/content/app/global.css +29 -15
  94. package/corpus/templates/content/changelog/2026-06-29-database-gallery-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  95. package/corpus/templates/content/changelog/2026-06-29-read-only-pages-no-longer-try-to-autosave-or-open-editor-onl.md +6 -0
  96. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +25 -20
  97. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -0
  98. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +8 -0
  99. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +13 -0
  100. package/corpus/templates/design/AGENTS.md +24 -14
  101. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1054 -0
  102. package/corpus/templates/design/actions/apply-visual-edit.ts +24 -4
  103. package/corpus/templates/design/actions/edit-design.ts +6 -3
  104. package/corpus/templates/design/actions/export-coding-handoff.ts +12 -4
  105. package/corpus/templates/design/actions/generate-design.ts +6 -21
  106. package/corpus/templates/design/actions/generate-screens.ts +48 -5
  107. package/corpus/templates/design/actions/get-design.ts +1 -0
  108. package/corpus/templates/design/actions/present-design-variants.ts +355 -30
  109. package/corpus/templates/design/actions/update-design.ts +47 -10
  110. package/corpus/templates/design/actions/update-file.ts +33 -8
  111. package/corpus/templates/design/actions/view-screen.ts +89 -31
  112. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +389 -71
  113. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +1 -1
  114. package/corpus/templates/design/app/components/design/EditPanel.tsx +596 -95
  115. package/corpus/templates/design/app/components/design/LayersPanel.tsx +428 -221
  116. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1197 -228
  117. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +94 -217
  118. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +28 -14
  119. package/corpus/templates/design/app/components/design/index.ts +0 -1
  120. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +228 -170
  121. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +140 -13
  122. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +39 -8
  123. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +4 -2
  124. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +75 -5
  125. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +37 -12
  126. package/corpus/templates/design/app/components/design/inspector/index.ts +1 -0
  127. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +17 -4
  128. package/corpus/templates/design/app/components/design/types.ts +9 -0
  129. package/corpus/templates/design/app/components/layout/Layout.tsx +23 -5
  130. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +448 -259
  131. package/corpus/templates/design/app/global.css +77 -49
  132. package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
  133. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +8 -1
  134. package/corpus/templates/design/app/i18n/zh-TW.ts +4 -0
  135. package/corpus/templates/design/app/i18n-data.ts +55 -3
  136. package/corpus/templates/design/app/pages/DesignEditor.tsx +2783 -658
  137. package/corpus/templates/design/app/root.tsx +1 -1
  138. package/corpus/templates/design/changelog/2026-06-29-agent-edits-to-a-design-are-no-longer-occasionally-dropped-o.md +6 -0
  139. package/corpus/templates/design/changelog/2026-06-29-box-shadow-and-gradient-colors-keep-named-css-colors-like-re.md +6 -0
  140. package/corpus/templates/design/changelog/2026-06-29-containers-no-longer-block-selecting-nested-elements-and-nor.md +6 -0
  141. package/corpus/templates/design/changelog/2026-06-29-cut-cmd-ctrl-x-now-removes-the-selected-element-to-the-clipb.md +6 -0
  142. package/corpus/templates/design/changelog/2026-06-29-deleting-with-multiple-layers-selected-in-the-layers-panel-n.md +6 -0
  143. package/corpus/templates/design/changelog/2026-06-29-design-agents-now-generate-and-refine-variants-from-stronger.md +6 -0
  144. package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +6 -0
  145. package/corpus/templates/design/changelog/2026-06-29-design-questions-now-appear-as-a-cleaner-canvas-intake-with-.md +6 -0
  146. package/corpus/templates/design/changelog/2026-06-29-design-variant-directions-now-appear-as-board-screens-with-chat-buttons.md +5 -0
  147. package/corpus/templates/design/changelog/2026-06-29-duplicating-a-screen-now-gives-the-copy-its-own-layer-ids-so.md +6 -0
  148. package/corpus/templates/design/changelog/2026-06-29-element-selection-no-longer-snaps-back-to-the-previous-layer.md +6 -0
  149. package/corpus/templates/design/changelog/2026-06-29-frame-resize-measurements-now-stay-beside-the-cursor-while-d.md +6 -0
  150. package/corpus/templates/design/changelog/2026-06-29-hiding-and-re-showing-a-gradient-fill-now-preserves-each-sto.md +6 -0
  151. package/corpus/templates/design/changelog/2026-06-29-inspector-number-fields-have-cleaner-tooltips-and-draggable-.md +6 -0
  152. package/corpus/templates/design/changelog/2026-06-29-layer-locking-layer-nesting-generated-question-intake-and-im.md +6 -0
  153. package/corpus/templates/design/changelog/2026-06-29-layer-names-in-the-sidebar-can-scroll-horizontally-instead-o.md +6 -0
  154. package/corpus/templates/design/changelog/2026-06-29-layer-nesting-in-the-sidebar-now-uses-tighter-spacing-so-nam.md +6 -0
  155. package/corpus/templates/design/changelog/2026-06-29-layers-no-longer-repeat-document-wrapper-rows-when-a-screen-.md +6 -0
  156. package/corpus/templates/design/changelog/2026-06-29-overview-previews-now-keep-element-hover-and-selection-tied-.md +6 -0
  157. package/corpus/templates/design/changelog/2026-06-29-picking-a-design-direction-now-submits-to-the-agent-right-aw.md +6 -0
  158. package/corpus/templates/design/changelog/2026-06-29-pressing-escape-now-cancels-a-stuck-screen-drag-in-the-overv.md +6 -0
  159. package/corpus/templates/design/changelog/2026-06-29-screen-overview-is-easier-to-find-and-named-screens-open-dir.md +6 -0
  160. package/corpus/templates/design/changelog/2026-06-29-screen-overview-only-highlights-screens-when-the-frame-or-ti.md +6 -0
  161. package/corpus/templates/design/changelog/2026-06-29-screen-overview-previews-now-resize-to-match-the-selected-de.md +6 -0
  162. package/corpus/templates/design/changelog/2026-06-29-screen-overview-selection-now-uses-a-pointer-cursor-instead-.md +6 -0
  163. package/corpus/templates/design/changelog/2026-06-29-screen-overview-selection-outlines-now-stay-locked-to-the-se.md +6 -0
  164. package/corpus/templates/design/changelog/2026-06-29-screen-overview-zoom-now-reports-real-screen-scale-separatel.md +6 -0
  165. package/corpus/templates/design/changelog/2026-06-29-screen-overview-zooming-feels-smoother-and-selection-outline.md +6 -0
  166. package/corpus/templates/design/changelog/2026-06-29-screen-overview-zooming-now-stays-on-the-overview-canvas-ins.md +6 -0
  167. package/corpus/templates/design/changelog/2026-06-29-selecting-a-nested-layer-from-all-screens-now-keeps-you-in-a.md +6 -0
  168. package/corpus/templates/design/changelog/2026-06-29-share-now-groups-link-sharing-exports-and-agent-handoff.md +6 -0
  169. package/corpus/templates/design/changelog/2026-06-29-style-changes-that-set-several-properties-at-once-fixed-size.md +6 -0
  170. package/corpus/templates/design/changelog/2026-06-29-the-design-editor-loading-skeleton-now-uses-a-softer-charcoa.md +6 -0
  171. package/corpus/templates/design/changelog/2026-06-29-the-device-preview-menu-now-sits-next-to-the-preview-button-.md +6 -0
  172. package/corpus/templates/design/changelog/2026-06-29-the-editor-no-longer-crashes-when-a-screen-ends-up-with-dupl.md +6 -0
  173. package/corpus/templates/design/changelog/2026-06-29-the-inspector-stays-read-only-while-an-empty-design-is-still.md +6 -0
  174. package/corpus/templates/design/changelog/2026-06-29-the-missing-design-view-now-matches-the-rest-of-the-editor-c.md +6 -0
  175. package/corpus/templates/design/changelog/2026-06-29-the-pen-tool-icon-and-tool-option-menus-better-match-the-edi.md +6 -0
  176. package/corpus/templates/design/changelog/2026-06-29-typography-controls-now-keep-the-font-name-visible-and-move-.md +6 -0
  177. package/corpus/templates/design/e2e/README.md +4 -3
  178. package/corpus/templates/design/e2e/global-setup.ts +24 -23
  179. package/corpus/templates/design/e2e/helpers.ts +122 -11
  180. package/corpus/templates/design/playwright.config.ts +19 -4
  181. package/corpus/templates/design/server/lib/coding-handoff.ts +126 -2
  182. package/corpus/templates/design/server/plugins/core-routes.ts +1 -2
  183. package/corpus/templates/design/server/routes/api/design-handoff/[id].zip.get.ts +86 -0
  184. package/corpus/templates/design/shared/code-layer.ts +125 -22
  185. package/corpus/templates/design/shared/color-utils.ts +159 -0
  186. package/corpus/templates/design/shared/generation-session.ts +42 -0
  187. package/corpus/templates/dispatch/app/global.css +79 -35
  188. package/corpus/templates/dispatch/app/routes/integrations.tsx +8 -7
  189. package/corpus/templates/dispatch/changelog/2026-06-29-integration-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  190. package/corpus/templates/forms/app/components/layout/Layout.tsx +6 -2
  191. package/corpus/templates/forms/app/global.css +64 -35
  192. package/corpus/templates/forms/app/pages/FormsListPage.tsx +2 -2
  193. package/corpus/templates/forms/app/pages/ResponseInsightsPage.tsx +3 -3
  194. package/corpus/templates/forms/changelog/2026-06-29-form-list-and-insight-layouts-adapt-when-the-agent-sidebar.md +6 -0
  195. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +3 -1
  196. package/corpus/templates/macros/app/global.css +25 -9
  197. package/corpus/templates/macros/changelog/2026-06-29-macro-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  198. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +6 -2
  199. package/corpus/templates/mail/app/global.css +42 -36
  200. package/corpus/templates/mail/app/pages/InboxPage.tsx +1 -1
  201. package/corpus/templates/mail/changelog/2026-06-29-the-contact-panel-now-adapts-to-the-available-mail-pane-width.md +6 -0
  202. package/corpus/templates/plan/app/components/layout/Layout.tsx +3 -1
  203. package/corpus/templates/plan/app/components/plan/DocumentArea.tsx +6 -7
  204. package/corpus/templates/plan/app/global.css +74 -42
  205. package/corpus/templates/plan/changelog/2026-06-29-plan-document-blocks-adapt-to-the-available-document-width.md +6 -0
  206. package/corpus/templates/plan/changelog/2026-06-29-plan-titles-and-summaries-can-be-edited-inline-without-focus.md +6 -0
  207. package/corpus/templates/slides/app/components/layout/Layout.tsx +3 -1
  208. package/corpus/templates/slides/app/global.css +49 -33
  209. package/corpus/templates/slides/changelog/2026-06-29-slide-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  210. package/corpus/templates/videos/app/components/layout/Layout.tsx +3 -1
  211. package/corpus/templates/videos/app/global.css +35 -35
  212. package/corpus/templates/videos/changelog/2026-06-29-video-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  213. package/dist/cli/mcp.d.ts.map +1 -1
  214. package/dist/cli/mcp.js +10 -0
  215. package/dist/cli/mcp.js.map +1 -1
  216. package/dist/cli/skills.d.ts.map +1 -1
  217. package/dist/cli/skills.js +58 -12
  218. package/dist/cli/skills.js.map +1 -1
  219. package/dist/client/AssistantChat.d.ts.map +1 -1
  220. package/dist/client/AssistantChat.js +54 -24
  221. package/dist/client/AssistantChat.js.map +1 -1
  222. package/dist/client/analytics.d.ts.map +1 -1
  223. package/dist/client/analytics.js +2 -1
  224. package/dist/client/analytics.js.map +1 -1
  225. package/dist/client/error-format.d.ts.map +1 -1
  226. package/dist/client/error-format.js +17 -0
  227. package/dist/client/error-format.js.map +1 -1
  228. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  229. package/dist/client/extensions/ExtensionViewer.js +3 -4
  230. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  231. package/dist/client/guided-questions.d.ts +4 -0
  232. package/dist/client/guided-questions.d.ts.map +1 -1
  233. package/dist/client/guided-questions.js +16 -7
  234. package/dist/client/guided-questions.js.map +1 -1
  235. package/dist/client/onboarding/OnboardingPanel.js +26 -1
  236. package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
  237. package/dist/client/resources/BuiltinCapabilityDetail.d.ts.map +1 -1
  238. package/dist/client/resources/BuiltinCapabilityDetail.js +1 -1
  239. package/dist/client/resources/BuiltinCapabilityDetail.js.map +1 -1
  240. package/dist/client/resources/use-builtin-capabilities.d.ts +1 -1
  241. package/dist/client/resources/use-builtin-capabilities.d.ts.map +1 -1
  242. package/dist/client/resources/use-builtin-capabilities.js +2 -1
  243. package/dist/client/resources/use-builtin-capabilities.js.map +1 -1
  244. package/dist/client/session-replay.d.ts.map +1 -1
  245. package/dist/client/session-replay.js +31 -0
  246. package/dist/client/session-replay.js.map +1 -1
  247. package/dist/client/sharing/ShareButton.d.ts +14 -0
  248. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  249. package/dist/client/sharing/ShareButton.js +36 -7
  250. package/dist/client/sharing/ShareButton.js.map +1 -1
  251. package/dist/coding-tools/index.js +1 -1
  252. package/dist/coding-tools/index.js.map +1 -1
  253. package/dist/collab/routes.d.ts +1 -1
  254. package/dist/mcp/index.d.ts +2 -0
  255. package/dist/mcp/index.d.ts.map +1 -1
  256. package/dist/mcp/index.js +1 -0
  257. package/dist/mcp/index.js.map +1 -1
  258. package/dist/mcp/screen-memory-stdio.d.ts +6 -0
  259. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -0
  260. package/dist/mcp/screen-memory-stdio.js +213 -0
  261. package/dist/mcp/screen-memory-stdio.js.map +1 -0
  262. package/dist/mcp-client/builtin-capabilities.d.ts +1 -1
  263. package/dist/mcp-client/builtin-capabilities.d.ts.map +1 -1
  264. package/dist/mcp-client/builtin-capabilities.js +10 -0
  265. package/dist/mcp-client/builtin-capabilities.js.map +1 -1
  266. package/dist/mcp-client/index.d.ts +1 -0
  267. package/dist/mcp-client/index.d.ts.map +1 -1
  268. package/dist/mcp-client/index.js +1 -0
  269. package/dist/mcp-client/index.js.map +1 -1
  270. package/dist/mcp-client/screen-memory-local.d.ts +61 -0
  271. package/dist/mcp-client/screen-memory-local.d.ts.map +1 -0
  272. package/dist/mcp-client/screen-memory-local.js +336 -0
  273. package/dist/mcp-client/screen-memory-local.js.map +1 -0
  274. package/dist/onboarding/types.d.ts +7 -0
  275. package/dist/onboarding/types.d.ts.map +1 -1
  276. package/dist/onboarding/types.js.map +1 -1
  277. package/dist/resources/handlers.d.ts +2 -2
  278. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  279. package/dist/server/agent-chat-plugin.js +26 -0
  280. package/dist/server/agent-chat-plugin.js.map +1 -1
  281. package/dist/server/agents-bundle.d.ts.map +1 -1
  282. package/dist/server/agents-bundle.js +22 -7
  283. package/dist/server/agents-bundle.js.map +1 -1
  284. package/dist/server/transcribe-voice.d.ts +1 -1
  285. package/dist/styles/agent-native.css +5 -0
  286. package/dist/templates/default/app/global.css +35 -35
  287. package/docs/content/locales/ar-SA/template-design.mdx +6 -0
  288. package/docs/content/locales/de-DE/template-design.mdx +8 -0
  289. package/docs/content/locales/es-ES/template-design.mdx +10 -2
  290. package/docs/content/locales/fr-FR/template-design.mdx +10 -2
  291. package/docs/content/locales/hi-IN/template-design.mdx +7 -0
  292. package/docs/content/locales/ja-JP/template-design.mdx +7 -0
  293. package/docs/content/locales/ko-KR/template-design.mdx +7 -0
  294. package/docs/content/locales/pt-BR/template-design.mdx +9 -2
  295. package/docs/content/locales/zh-CN/template-design.mdx +6 -0
  296. package/docs/content/locales/zh-TW/template-design.mdx +6 -0
  297. package/docs/content/template-design.mdx +9 -2
  298. package/package.json +1 -1
  299. package/src/templates/default/app/global.css +35 -35
  300. package/corpus/templates/design/app/components/design/VariantGrid.tsx +0 -189
  301. package/corpus/templates/design/app/components/design/VariantHandoffCard.tsx +0 -115
  302. package/corpus/templates/design/app/hooks/use-variant-flow.ts +0 -258
@@ -7,12 +7,14 @@ import {
7
7
  IconLock,
8
8
  IconLockOpen,
9
9
  IconLayoutGrid,
10
+ IconPencil,
10
11
  IconPlus,
11
12
  IconSearch,
12
13
  } from "@tabler/icons-react";
13
14
  import {
14
15
  useCallback,
15
16
  useEffect,
17
+ useLayoutEffect,
16
18
  useMemo,
17
19
  useRef,
18
20
  useState,
@@ -23,6 +25,13 @@ import {
23
25
  } from "react";
24
26
 
25
27
  import { Button } from "@/components/ui/button";
28
+ import {
29
+ ContextMenu,
30
+ ContextMenuContent,
31
+ ContextMenuItem,
32
+ ContextMenuSeparator,
33
+ ContextMenuTrigger,
34
+ } from "@/components/ui/context-menu";
26
35
  import { Input } from "@/components/ui/input";
27
36
  import {
28
37
  Tooltip,
@@ -31,6 +40,9 @@ import {
31
40
  } from "@/components/ui/tooltip";
32
41
  import { cn } from "@/lib/utils";
33
42
 
43
+ const LAYER_ROW_BASE_INDENT = 4;
44
+ const LAYER_ROW_DEPTH_INDENT = 18;
45
+
34
46
  export type LayersPanelNodeType =
35
47
  | "file"
36
48
  | "screen"
@@ -94,6 +106,7 @@ export interface LayersPanelSelectionIntent {
94
106
  id: string;
95
107
  selectedIds: string[];
96
108
  additive: boolean;
109
+ currentSelectedIds?: string[];
97
110
  range: boolean;
98
111
  source: "keyboard" | "pointer";
99
112
  }
@@ -107,6 +120,7 @@ export interface LayersPanelMoveIntent {
107
120
  export interface LayersPanelLabels {
108
121
  title: string;
109
122
  screens: string;
123
+ allScreens: string;
110
124
  screenOverview: string;
111
125
  addScreen: string;
112
126
  searchPlaceholder: string;
@@ -128,6 +142,7 @@ export interface LayersPanelLabels {
128
142
  export interface LayersPanelProps {
129
143
  screens?: LayersPanelFile[];
130
144
  activeScreenId?: string;
145
+ screenOverviewActive?: boolean;
131
146
  files?: LayersPanelFile[];
132
147
  layers?: LayersPanelNode[];
133
148
  codeLayers?: LayersPanelNode[];
@@ -161,19 +176,45 @@ interface FlatLayerRow {
161
176
  depth: number;
162
177
  ancestorIds: string[];
163
178
  hasChildren: boolean;
179
+ canAcceptChildren: boolean;
164
180
  }
165
181
 
182
+ // Node types that can contain children even when currently empty.
183
+ // Leaf / void types (text, image, shape, rectangle) are excluded so we don't
184
+ // offer an "inside" drop zone on genuinely non-container elements.
185
+ const CONTAINER_TYPES = new Set<LayersPanelNodeType | undefined>([
186
+ "file",
187
+ "screen",
188
+ "frame",
189
+ "group",
190
+ "section",
191
+ "component",
192
+ "instance",
193
+ "code",
194
+ "element",
195
+ ]);
196
+
166
197
  const SECTION_CODE_ID = "__design_layers_code__";
167
198
  const SECTION_ELEMENT_ID = "__design_layers_elements__";
168
199
 
169
200
  // Module-level drag state: dataTransfer.getData() returns "" during dragover
170
201
  // per spec; the source row stores the drag payload here on dragstart instead.
171
202
  let activeDragState: { sourceId: string; draggedIds: string[] } | null = null;
203
+ let activeDropIntent: LayersPanelMoveIntent | null = null;
204
+
205
+ const ROW_BASE_INDENT = 4;
206
+ const ROW_INDENT_STEP = 28;
207
+ const ROW_MAX_INDENT = 96;
208
+
209
+ function rowIndent(depth: number): number {
210
+ return Math.min(ROW_BASE_INDENT + depth * ROW_INDENT_STEP, ROW_MAX_INDENT);
211
+ }
172
212
 
173
213
  function defaultLabels(t: ReturnType<typeof useT>): LayersPanelLabels {
174
214
  return {
175
215
  title: t("layersPanel.title"),
176
216
  screens: t("layersPanel.screens"),
217
+ allScreens: t("layersPanel.allScreens"),
177
218
  screenOverview: t("designEditor.screenOverview"),
178
219
  addScreen: t("layersPanel.addScreen"),
179
220
  searchPlaceholder: t("layersPanel.searchPlaceholder"),
@@ -307,8 +348,16 @@ function flattenRows(
307
348
  nodes.forEach((node, index) => {
308
349
  const children = node.children ?? [];
309
350
  const hasChildren = children.length > 0;
351
+ const canAcceptChildren = CONTAINER_TYPES.has(node.type);
310
352
  const rowKey = `${parentKey}/${node.id}:${index}`;
311
- rows.push({ node, rowKey, depth, ancestorIds, hasChildren });
353
+ rows.push({
354
+ node,
355
+ rowKey,
356
+ depth,
357
+ ancestorIds,
358
+ hasChildren,
359
+ canAcceptChildren,
360
+ });
312
361
  if (hasChildren && (forceExpanded || expandedIds.has(node.id))) {
313
362
  flattenRows(
314
363
  children,
@@ -374,6 +423,7 @@ function layerCanShowBadge(node: LayersPanelNode) {
374
423
  export function LayersPanel({
375
424
  screens,
376
425
  activeScreenId,
426
+ screenOverviewActive = false,
377
427
  files,
378
428
  layers,
379
429
  codeLayers,
@@ -400,6 +450,7 @@ export function LayersPanel({
400
450
  const t = useT();
401
451
  const labels = useMemo(() => mergeLabels(labelsProp, t), [labelsProp, t]);
402
452
  const selectedIdSet = useMemo(() => new Set(selectedIds), [selectedIds]);
453
+ const selectedIdsRef = useRef<readonly string[]>(selectedIds);
403
454
  const expandedIdSet = useMemo(() => new Set(expandedIds), [expandedIds]);
404
455
  const lastSelectionAnchorRef = useRef<string | null>(selectedIds[0] ?? null);
405
456
  const searchInputRef = useRef<HTMLInputElement>(null);
@@ -435,6 +486,10 @@ export function LayersPanel({
435
486
  [roots, selectedIdSet],
436
487
  );
437
488
 
489
+ useLayoutEffect(() => {
490
+ selectedIdsRef.current = selectedIds;
491
+ }, [selectedIds]);
492
+
438
493
  useEffect(() => {
439
494
  if (selectedAncestorIds.length === 0) return;
440
495
  const next = new Set(expandedIds);
@@ -481,33 +536,47 @@ export function LayersPanel({
481
536
  id: string,
482
537
  options: {
483
538
  additive: boolean;
539
+ currentSelectedIds?: string[];
484
540
  range: boolean;
485
541
  source: "keyboard" | "pointer";
486
542
  },
487
543
  ) => {
544
+ const currentSelectedIds =
545
+ options.currentSelectedIds ?? selectedIdsRef.current;
546
+ const currentSelectedIdSet = new Set(currentSelectedIds);
488
547
  let nextIds: string[];
489
548
  if (options.range && lastSelectionAnchorRef.current) {
490
- const from = selectableVisibleIds.indexOf(
491
- lastSelectionAnchorRef.current,
492
- );
549
+ let anchor = lastSelectionAnchorRef.current;
550
+ if (selectableVisibleIds.indexOf(anchor) < 0) {
551
+ // Stale anchor (deleted / filtered / collapsed out of view): pivot from
552
+ // the last selected layer that is still visible & selectable, matching
553
+ // Figma's behavior instead of dropping the range to a single select.
554
+ const fallback = [...selectedIds]
555
+ .reverse()
556
+ .find((sid) => selectableVisibleIds.includes(sid));
557
+ if (fallback) {
558
+ anchor = fallback;
559
+ lastSelectionAnchorRef.current = fallback;
560
+ }
561
+ }
562
+ const from = selectableVisibleIds.indexOf(anchor);
493
563
  const to = selectableVisibleIds.indexOf(id);
494
564
  if (from >= 0 && to >= 0) {
495
565
  const [start, end] = from < to ? [from, to] : [to, from];
496
566
  const rangeIds = selectableVisibleIds.slice(start, end + 1);
497
567
  nextIds = options.additive
498
- ? Array.from(new Set([...selectedIds, ...rangeIds]))
568
+ ? Array.from(new Set([...currentSelectedIds, ...rangeIds]))
499
569
  : rangeIds;
500
570
  } else {
501
571
  nextIds = [id];
502
572
  }
503
573
  } else if (options.additive) {
504
- nextIds = selectedIdSet.has(id)
505
- ? selectedIds.filter((selectedId) => selectedId !== id)
506
- : [...selectedIds, id];
574
+ nextIds = currentSelectedIdSet.has(id)
575
+ ? currentSelectedIds.filter((selectedId) => selectedId !== id)
576
+ : [...currentSelectedIds, id];
507
577
  } else {
508
578
  nextIds = [id];
509
579
  }
510
-
511
580
  // Only advance the anchor on plain clicks; Shift+clicks extend from the
512
581
  // existing anchor so the pivot stays fixed across consecutive range clicks.
513
582
  if (!options.range) {
@@ -515,7 +584,7 @@ export function LayersPanel({
515
584
  }
516
585
  onSelectionChange(nextIds, { id, selectedIds: nextIds, ...options });
517
586
  },
518
- [onSelectionChange, selectableVisibleIds, selectedIdSet, selectedIds],
587
+ [onSelectionChange, selectableVisibleIds],
519
588
  );
520
589
 
521
590
  const commitRename = useCallback(
@@ -599,12 +668,6 @@ export function LayersPanel({
599
668
  {labels.screens}
600
669
  </h2>
601
670
  <div className="flex items-center gap-0.5 text-muted-foreground">
602
- <IconTooltipButton
603
- label={labels.screenOverview}
604
- onClick={onScreenOverview}
605
- >
606
- <IconLayoutGrid className="size-3.5" />
607
- </IconTooltipButton>
608
671
  <IconTooltipButton
609
672
  label={labels.searchPlaceholder}
610
673
  onClick={openSearch}
@@ -620,9 +683,30 @@ export function LayersPanel({
620
683
  </IconTooltipButton>
621
684
  </div>
622
685
  </div>
686
+ <div className="px-2">
687
+ <button
688
+ type="button"
689
+ className={cn(
690
+ "flex h-8 w-full cursor-default items-center gap-2 rounded-[5px] px-2 text-left text-[12px] font-semibold outline-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]",
691
+ screenOverviewActive
692
+ ? "bg-[var(--design-editor-active-row-color)] text-foreground"
693
+ : "text-foreground/85 hover:bg-[var(--design-editor-active-row-color)] hover:text-foreground",
694
+ )}
695
+ aria-current={screenOverviewActive ? "page" : undefined}
696
+ onClick={() => onScreenOverview?.()}
697
+ title={labels.allScreens}
698
+ >
699
+ <IconLayoutGrid className="size-4 shrink-0" />
700
+ <span className="min-w-0 flex-1 truncate">
701
+ {labels.allScreens}
702
+ </span>
703
+ </button>
704
+ </div>
705
+ <div className="mx-3 my-2 border-t border-[var(--design-editor-panel-divider-color)]" />
623
706
  <div className="space-y-0.5 px-2">
624
707
  {screenRows.map((screen) => {
625
- const isActive = screen.id === activeScreenId;
708
+ const isActive =
709
+ !screenOverviewActive && screen.id === activeScreenId;
626
710
  return (
627
711
  <button
628
712
  key={screen.id}
@@ -633,6 +717,7 @@ export function LayersPanel({
633
717
  ? "bg-[var(--design-editor-active-row-color)] text-foreground"
634
718
  : "text-foreground/85 hover:bg-[var(--design-editor-active-row-color)] hover:text-foreground",
635
719
  )}
720
+ aria-current={isActive ? "page" : undefined}
636
721
  onClick={() => onScreenSelect?.(screen.id)}
637
722
  title={screen.filename ?? screen.name}
638
723
  >
@@ -697,9 +782,13 @@ export function LayersPanel({
697
782
  </div>
698
783
  ) : null}
699
784
 
700
- <div className="min-h-0 flex-1 overflow-y-auto py-2">
785
+ <div className="min-h-0 flex-1 overflow-auto overscroll-contain py-2">
701
786
  {visibleRows.length ? (
702
- <div className="px-2" role="tree" aria-label={labels.title}>
787
+ <div
788
+ className="w-max min-w-full px-2"
789
+ role="tree"
790
+ aria-label={labels.title}
791
+ >
703
792
  {visibleRows.map((row) => (
704
793
  <LayerRow
705
794
  key={row.rowKey}
@@ -723,6 +812,7 @@ export function LayersPanel({
723
812
  onCommitRename={commitRename}
724
813
  onCancelRename={() => setRenamingId(null)}
725
814
  onStartRename={startRename}
815
+ onRename={onRename}
726
816
  onSelect={selectNode}
727
817
  onToggleExpanded={(expanded) =>
728
818
  onExpandedIdsChange(
@@ -766,6 +856,7 @@ function LayerRow({
766
856
  onCommitRename,
767
857
  onCancelRename,
768
858
  onStartRename,
859
+ onRename,
769
860
  onSelect,
770
861
  onToggleExpanded,
771
862
  onToggleLocked,
@@ -792,10 +883,12 @@ function LayerRow({
792
883
  onCommitRename: (id: string) => void;
793
884
  onCancelRename: () => void;
794
885
  onStartRename: (node: LayersPanelNode) => void;
886
+ onRename?: (id: string, name: string) => void;
795
887
  onSelect: (
796
888
  id: string,
797
889
  options: {
798
890
  additive: boolean;
891
+ currentSelectedIds?: string[];
799
892
  range: boolean;
800
893
  source: "keyboard" | "pointer";
801
894
  },
@@ -812,21 +905,42 @@ function LayerRow({
812
905
  selectedIds: readonly string[];
813
906
  visibleRows: FlatLayerRow[];
814
907
  }) {
815
- const { node, depth, hasChildren } = row;
908
+ const { node, depth, hasChildren, canAcceptChildren } = row;
816
909
  const selectable = node.selectable !== false;
817
910
  const lockable = node.lockable !== false && Boolean(onToggleLocked);
818
911
  const hideable = node.hideable !== false && Boolean(onToggleHidden);
819
912
  const draggable = selectable && Boolean(onMoveLayer);
913
+ const canDropInside = layerCanDropInside(node, hasChildren);
820
914
  const activeDrop =
821
915
  dropIndicator?.targetId === node.id ? dropIndicator.placement : null;
822
916
  // Tracks whether the user pressed Escape to cancel rename so that the
823
917
  // subsequent blur event does not commit the edit.
824
918
  const renameCancelledRef = useRef(false);
825
919
 
920
+ const readSelectedIdsFromTree = (target: HTMLElement): string[] => {
921
+ const tree = target.closest('[role="tree"]');
922
+ if (!tree) return selectedIds.filter((id) => !id.startsWith("__"));
923
+ return Array.from(
924
+ tree.querySelectorAll<HTMLElement>(
925
+ '[role="treeitem"][aria-selected="true"] [data-layer-row-button][data-layer-node-id]',
926
+ ),
927
+ )
928
+ .map((button) => button.dataset.layerNodeId)
929
+ .filter((id): id is string => Boolean(id && !id.startsWith("__")));
930
+ };
931
+
826
932
  const handlePointerSelect = (event: MouseEvent<HTMLButtonElement>) => {
827
933
  if (!selectable) return;
934
+ if (event.detail === 0) return;
935
+ const nativeEvent = event.nativeEvent;
936
+ const additive =
937
+ event.metaKey ||
938
+ event.ctrlKey ||
939
+ nativeEvent.metaKey ||
940
+ nativeEvent.ctrlKey;
828
941
  onSelect(node.id, {
829
- additive: event.metaKey || event.ctrlKey,
942
+ additive,
943
+ currentSelectedIds: readSelectedIdsFromTree(event.currentTarget),
830
944
  range: event.shiftKey,
831
945
  source: "pointer",
832
946
  });
@@ -857,11 +971,12 @@ function LayerRow({
857
971
  (visibleRow) => visibleRow.rowKey === row.rowKey,
858
972
  );
859
973
 
860
- if (event.key === "Enter" || event.key === " ") {
974
+ if (event.key === "Enter" || event.key === " " || event.key === "Space") {
861
975
  event.preventDefault();
862
976
  if (!selectable) return;
863
977
  onSelect(node.id, {
864
978
  additive: event.metaKey || event.ctrlKey,
979
+ currentSelectedIds: readSelectedIdsFromTree(event.currentTarget),
865
980
  range: event.shiftKey,
866
981
  source: "keyboard",
867
982
  });
@@ -959,11 +1074,12 @@ function LayerRow({
959
1074
  const intent = {
960
1075
  draggedIds: cleanedIds,
961
1076
  targetId: node.id,
962
- placement: dropPlacementForEvent(event, hasChildren),
1077
+ placement: dropPlacementForEvent(event, canDropInside),
963
1078
  } satisfies LayersPanelMoveIntent;
964
1079
  if (canMoveLayer && !canMoveLayer(intent)) return;
965
1080
  event.preventDefault();
966
1081
  event.dataTransfer.dropEffect = "move";
1082
+ activeDropIntent = intent;
967
1083
  onDropIndicatorChange(intent);
968
1084
  };
969
1085
 
@@ -992,233 +1108,312 @@ function LayerRow({
992
1108
  (id) => id && id !== node.id && !id.startsWith("__"),
993
1109
  );
994
1110
  if (cleanedIds.length > 0) {
995
- // Recompute placement from the drop event position rather than relying on
996
- // React state (dropIndicator) which may be stale or null.
997
- const intent = {
998
- draggedIds: cleanedIds,
999
- targetId: node.id,
1000
- placement: dropPlacementForEvent(event, hasChildren),
1001
- } satisfies LayersPanelMoveIntent;
1111
+ const storedIntent =
1112
+ activeDropIntent?.targetId === node.id
1113
+ ? {
1114
+ ...activeDropIntent,
1115
+ draggedIds: activeDropIntent.draggedIds.filter((id) =>
1116
+ cleanedIds.includes(id),
1117
+ ),
1118
+ }
1119
+ : null;
1120
+ const intent =
1121
+ storedIntent && storedIntent.draggedIds.length > 0
1122
+ ? storedIntent
1123
+ : ({
1124
+ draggedIds: cleanedIds,
1125
+ targetId: node.id,
1126
+ placement: dropPlacementForEvent(event, canDropInside),
1127
+ } satisfies LayersPanelMoveIntent);
1002
1128
  if (!canMoveLayer || canMoveLayer(intent)) {
1003
1129
  onMoveLayer(intent);
1004
1130
  }
1005
1131
  }
1132
+ activeDropIntent = null;
1006
1133
  onDropIndicatorChange(null);
1007
1134
  };
1008
1135
 
1009
- return (
1010
- <div
1011
- ref={rowRef}
1012
- role="treeitem"
1013
- aria-expanded={hasChildren ? isExpanded : undefined}
1014
- aria-level={depth + 1}
1015
- aria-selected={selectable ? isSelected : undefined}
1016
- className="relative"
1017
- draggable={draggable}
1018
- onDragStart={handleDragStart}
1019
- onDragOver={handleDragOver}
1020
- onDragLeave={(event) => {
1021
- // Only clear the indicator when the pointer truly leaves this row.
1022
- // Moving into a child element fires dragleave on the outer div too, so
1023
- // we suppress the clear when relatedTarget is still within this row.
1024
- if (event.currentTarget.contains(event.relatedTarget as Node | null))
1025
- return;
1026
- onDropIndicatorChange(null);
1027
- }}
1028
- onDrop={handleDrop}
1029
- onDragEnd={() => {
1030
- activeDragState = null;
1031
- onDropIndicatorChange(null);
1032
- }}
1033
- onMouseEnter={() => onHoverLayer?.(node.id)}
1034
- onMouseLeave={() => onLeaveLayer?.(node.id)}
1035
- >
1036
- {activeDrop === "before" ? (
1037
- <span className="pointer-events-none absolute left-2 right-2 top-0 z-10 h-px bg-[var(--design-editor-accent-color)]" />
1038
- ) : null}
1039
- {activeDrop === "after" ? (
1040
- <span className="pointer-events-none absolute bottom-0 left-2 right-2 z-10 h-px bg-[var(--design-editor-accent-color)]" />
1041
- ) : null}
1042
- <div
1043
- className={cn(
1044
- "group flex h-8 items-center gap-1 rounded-[5px] pr-1 text-[12px]",
1045
- activeDrop === "inside" &&
1046
- "ring-1 ring-inset ring-[var(--design-editor-accent-color)]",
1047
- isSelected &&
1048
- "bg-[var(--design-editor-selection-color)] text-foreground",
1049
- !isSelected &&
1050
- isInSelectedSubtree &&
1051
- "bg-[var(--design-editor-selected-subtree-color)] text-foreground/95",
1052
- !isSelected &&
1053
- isActiveScreen &&
1054
- "bg-[var(--design-editor-active-row-color)] text-foreground hover:bg-[var(--design-editor-active-row-color)]",
1055
- !isSelected &&
1056
- !isInSelectedSubtree &&
1057
- !isActiveScreen &&
1058
- "text-foreground/90 hover:bg-[var(--design-editor-layer-hover-color)] hover:text-foreground",
1059
- node.hidden && "text-muted-foreground",
1060
- )}
1061
- style={{ paddingLeft: 4 + depth * 28 }}
1062
- >
1063
- {hasChildren ? (
1064
- <Button
1065
- type="button"
1066
- variant="ghost"
1067
- size="icon"
1068
- className="size-4 shrink-0 rounded-sm p-0 text-muted-foreground hover:bg-transparent hover:text-foreground"
1069
- aria-label={isExpanded ? labels.collapse : labels.expand}
1070
- onClick={(event) => {
1071
- event.stopPropagation();
1072
- onToggleExpanded(!isExpanded);
1073
- }}
1074
- >
1075
- {isExpanded ? (
1076
- <IconChevronDown className="size-4" />
1077
- ) : (
1078
- <IconChevronRight className="size-4 rtl:-scale-x-100" />
1079
- )}
1080
- </Button>
1081
- ) : (
1082
- <span className="size-4 shrink-0" />
1083
- )}
1136
+ const showContextMenu =
1137
+ selectable &&
1138
+ (Boolean(onRename && node.renamable !== false) || lockable || hideable);
1084
1139
 
1085
- <button
1086
- type="button"
1087
- disabled={!selectable}
1088
- data-layer-row-button
1089
- data-layer-node-id={node.id}
1090
- className={cn(
1091
- "flex min-w-0 flex-1 items-center gap-2 overflow-hidden rounded-sm px-0.5 py-0 text-left outline-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]",
1092
- selectable ? "cursor-default" : "cursor-default opacity-80",
1093
- )}
1094
- onClick={handlePointerSelect}
1095
- onDoubleClick={() => onStartRename(node)}
1096
- onKeyDown={handleKeyDown}
1140
+ return (
1141
+ <ContextMenu>
1142
+ <ContextMenuTrigger asChild disabled={!showContextMenu}>
1143
+ <div
1144
+ ref={rowRef}
1145
+ role="treeitem"
1146
+ aria-expanded={hasChildren ? isExpanded : undefined}
1147
+ aria-level={depth + 1}
1148
+ aria-selected={selectable ? isSelected : undefined}
1149
+ className="relative min-w-full"
1150
+ draggable={draggable}
1151
+ onDragStart={handleDragStart}
1152
+ onDragOver={handleDragOver}
1153
+ onDragLeave={(event) => {
1154
+ // Only clear the indicator when the pointer truly leaves this row.
1155
+ // Moving into a child element fires dragleave on the outer div too, so
1156
+ // we suppress the clear when relatedTarget is still within this row.
1157
+ if (
1158
+ event.currentTarget.contains(event.relatedTarget as Node | null)
1159
+ )
1160
+ return;
1161
+ if (activeDropIntent?.targetId === node.id) activeDropIntent = null;
1162
+ onDropIndicatorChange(null);
1163
+ }}
1164
+ onDrop={handleDrop}
1165
+ onDragEnd={() => {
1166
+ activeDragState = null;
1167
+ activeDropIntent = null;
1168
+ onDropIndicatorChange(null);
1169
+ }}
1170
+ onMouseEnter={() => onHoverLayer?.(node.id)}
1171
+ onMouseLeave={() => onLeaveLayer?.(node.id)}
1097
1172
  >
1098
- <span
1173
+ {activeDrop === "before" ? (
1174
+ <span className="pointer-events-none absolute left-2 right-2 top-0 z-10 h-px bg-[var(--design-editor-accent-color)]" />
1175
+ ) : null}
1176
+ {activeDrop === "after" ? (
1177
+ <span className="pointer-events-none absolute bottom-0 left-2 right-2 z-10 h-px bg-[var(--design-editor-accent-color)]" />
1178
+ ) : null}
1179
+ <div
1099
1180
  className={cn(
1100
- "shrink-0 text-muted-foreground",
1101
- (isSelected || isInSelectedSubtree) && "text-foreground",
1181
+ "group flex h-8 min-w-full items-center gap-1 rounded-[5px] pr-1 text-[12px]",
1182
+ activeDrop === "inside" &&
1183
+ "ring-1 ring-inset ring-[var(--design-editor-accent-color)]",
1184
+ isSelected &&
1185
+ "bg-[var(--design-editor-selection-color)] text-foreground",
1186
+ !isSelected &&
1187
+ isInSelectedSubtree &&
1188
+ "bg-[var(--design-editor-selected-subtree-color)] text-foreground/95",
1189
+ !isSelected &&
1190
+ isActiveScreen &&
1191
+ "bg-[var(--design-editor-active-row-color)] text-foreground hover:bg-[var(--design-editor-active-row-color)]",
1192
+ !isSelected &&
1193
+ !isInSelectedSubtree &&
1194
+ !isActiveScreen &&
1195
+ "text-foreground/90 hover:bg-[var(--design-editor-layer-hover-color)] hover:text-foreground",
1196
+ node.hidden && "text-muted-foreground",
1102
1197
  )}
1198
+ style={{ paddingLeft: rowIndent(depth) }}
1103
1199
  >
1104
- {node.icon ?? (
1105
- <LayerGlyph
1106
- node={node}
1107
- selected={isSelected}
1108
- inSelectedSubtree={isInSelectedSubtree}
1109
- />
1110
- )}
1111
- </span>
1112
- {isRenaming ? (
1113
- <input
1114
- autoFocus
1115
- value={renameDraft}
1116
- onClick={(event) => event.stopPropagation()}
1117
- onChange={(event) => onRenameDraftChange(event.target.value)}
1118
- onBlur={() => {
1119
- // Escape sets renameCancelledRef before blur fires; skip commit.
1120
- if (renameCancelledRef.current) {
1121
- renameCancelledRef.current = false;
1122
- return;
1123
- }
1124
- onCommitRename(node.id);
1125
- }}
1126
- onKeyDown={(event) => {
1127
- if (event.key === "Enter") {
1128
- event.preventDefault();
1129
- onCommitRename(node.id);
1130
- } else if (event.key === "Escape") {
1131
- event.preventDefault();
1132
- renameCancelledRef.current = true;
1133
- onCancelRename();
1134
- }
1135
- }}
1136
- className="h-6 min-w-0 flex-1 rounded-[4px] border border-[var(--design-editor-accent-color)] bg-[var(--design-editor-panel-bg)] px-1.5 text-[12px] text-foreground outline-none"
1137
- aria-label={labels.rename}
1138
- />
1139
- ) : (
1140
- <span
1141
- className={cn(
1142
- "min-w-0 flex-1 truncate font-medium leading-none",
1143
- node.hidden && "line-through",
1144
- )}
1145
- >
1146
- {node.name}
1147
- </span>
1148
- )}
1149
- {!isRenaming &&
1150
- layerCanShowBadge(node) &&
1151
- node.badge !== null &&
1152
- node.badge !== undefined ? (
1153
- <span className="shrink-0 rounded-sm bg-muted px-1 text-[10px] text-muted-foreground">
1154
- {node.badge}
1155
- </span>
1156
- ) : null}
1157
- </button>
1158
-
1159
- {lockable ? (
1160
- <Tooltip>
1161
- <TooltipTrigger asChild>
1200
+ {hasChildren ? (
1162
1201
  <Button
1163
1202
  type="button"
1164
1203
  variant="ghost"
1165
1204
  size="icon"
1166
- className={cn(
1167
- "size-5 shrink-0 rounded-sm p-0 text-muted-foreground opacity-0 hover:bg-transparent hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100",
1168
- node.locked && "opacity-100",
1169
- isSelected && "text-foreground",
1170
- )}
1171
- aria-label={node.locked ? labels.unlock : labels.lock}
1205
+ className="size-4 shrink-0 rounded-sm p-0 text-muted-foreground hover:bg-transparent hover:text-foreground"
1206
+ aria-label={isExpanded ? labels.collapse : labels.expand}
1172
1207
  onClick={(event) => {
1173
1208
  event.stopPropagation();
1174
- onToggleLocked?.(node.id, !node.locked);
1209
+ onToggleExpanded(!isExpanded);
1175
1210
  }}
1176
1211
  >
1177
- {node.locked ? (
1178
- <IconLock className="size-3" />
1212
+ {isExpanded ? (
1213
+ <IconChevronDown className="size-4" />
1179
1214
  ) : (
1180
- <IconLockOpen className="size-3" />
1215
+ <IconChevronRight className="size-4 rtl:-scale-x-100" />
1181
1216
  )}
1182
1217
  </Button>
1183
- </TooltipTrigger>
1184
- <TooltipContent>
1185
- {node.locked ? labels.unlock : labels.lock}
1186
- </TooltipContent>
1187
- </Tooltip>
1188
- ) : null}
1218
+ ) : (
1219
+ <span className="size-4 shrink-0" />
1220
+ )}
1189
1221
 
1190
- {hideable ? (
1191
- <Tooltip>
1192
- <TooltipTrigger asChild>
1193
- <Button
1194
- type="button"
1195
- variant="ghost"
1196
- size="icon"
1222
+ <button
1223
+ type="button"
1224
+ disabled={!selectable}
1225
+ data-layer-row-button
1226
+ data-layer-node-id={node.id}
1227
+ className={cn(
1228
+ "flex min-w-0 flex-1 items-center gap-2 rounded-sm px-0.5 py-0 text-left outline-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]",
1229
+ selectable ? "cursor-default" : "cursor-default opacity-80",
1230
+ )}
1231
+ onClick={handlePointerSelect}
1232
+ onDoubleClick={() => onStartRename(node)}
1233
+ onKeyDown={handleKeyDown}
1234
+ >
1235
+ <span
1197
1236
  className={cn(
1198
- "size-5 shrink-0 rounded-sm p-0 text-muted-foreground opacity-0 hover:bg-transparent hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100",
1199
- node.hidden && "opacity-100",
1200
- isSelected && "text-foreground",
1237
+ "shrink-0 text-muted-foreground",
1238
+ (isSelected || isInSelectedSubtree) && "text-foreground",
1201
1239
  )}
1202
- aria-label={node.hidden ? labels.show : labels.hide}
1203
- onClick={(event) => {
1204
- event.stopPropagation();
1205
- onToggleHidden?.(node.id, !node.hidden);
1206
- }}
1207
1240
  >
1208
- {node.hidden ? (
1209
- <IconEyeOff className="size-3" />
1210
- ) : (
1211
- <IconEye className="size-3" />
1241
+ {node.icon ?? (
1242
+ <LayerGlyph
1243
+ node={node}
1244
+ selected={isSelected}
1245
+ inSelectedSubtree={isInSelectedSubtree}
1246
+ />
1212
1247
  )}
1213
- </Button>
1214
- </TooltipTrigger>
1215
- <TooltipContent>
1248
+ </span>
1249
+ {isRenaming ? (
1250
+ <input
1251
+ autoFocus
1252
+ value={renameDraft}
1253
+ onClick={(event) => event.stopPropagation()}
1254
+ onChange={(event) => onRenameDraftChange(event.target.value)}
1255
+ onBlur={() => {
1256
+ // Escape sets renameCancelledRef before blur fires; skip commit.
1257
+ if (renameCancelledRef.current) {
1258
+ renameCancelledRef.current = false;
1259
+ return;
1260
+ }
1261
+ onCommitRename(node.id);
1262
+ }}
1263
+ onKeyDown={(event) => {
1264
+ if (event.key === "Enter") {
1265
+ event.preventDefault();
1266
+ // Stop propagation so the keydown does not bubble up to the
1267
+ // parent row <button>'s handleKeyDown, which would fire
1268
+ // onSelect and potentially trigger canvas-level side effects
1269
+ // (e.g. switching to overview mode or selecting a wrong layer).
1270
+ event.stopPropagation();
1271
+ onCommitRename(node.id);
1272
+ } else if (event.key === "Tab") {
1273
+ // Commit the rename on Tab (Figma behavior) and prevent the
1274
+ // keydown from reaching the global design hotkeys handler which
1275
+ // would cycle the active file when Tab fires outside an input.
1276
+ event.preventDefault();
1277
+ event.stopPropagation();
1278
+ onCommitRename(node.id);
1279
+ } else if (event.key === "Escape") {
1280
+ event.preventDefault();
1281
+ event.stopPropagation();
1282
+ renameCancelledRef.current = true;
1283
+ onCancelRename();
1284
+ }
1285
+ }}
1286
+ className="h-6 min-w-0 flex-1 rounded-[4px] border border-[var(--design-editor-accent-color)] bg-[var(--design-editor-panel-bg)] px-1.5 text-[12px] text-foreground outline-none"
1287
+ aria-label={labels.rename}
1288
+ />
1289
+ ) : (
1290
+ <span
1291
+ className={cn(
1292
+ "min-w-0 flex-1 truncate font-medium leading-none",
1293
+ node.hidden && "line-through",
1294
+ )}
1295
+ title={node.name}
1296
+ >
1297
+ {node.name}
1298
+ </span>
1299
+ )}
1300
+ {!isRenaming &&
1301
+ layerCanShowBadge(node) &&
1302
+ node.badge !== null &&
1303
+ node.badge !== undefined ? (
1304
+ <span className="shrink-0 rounded-sm bg-muted px-1 text-[10px] text-muted-foreground">
1305
+ {node.badge}
1306
+ </span>
1307
+ ) : null}
1308
+ </button>
1309
+
1310
+ {lockable ? (
1311
+ <Tooltip>
1312
+ <TooltipTrigger asChild>
1313
+ <Button
1314
+ type="button"
1315
+ variant="ghost"
1316
+ size="icon"
1317
+ className={cn(
1318
+ "size-5 shrink-0 rounded-sm p-0 text-muted-foreground opacity-0 hover:bg-transparent hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100",
1319
+ node.locked && "opacity-100",
1320
+ isSelected && "text-foreground",
1321
+ )}
1322
+ aria-label={node.locked ? labels.unlock : labels.lock}
1323
+ onClick={(event) => {
1324
+ event.stopPropagation();
1325
+ onToggleLocked?.(node.id, !node.locked);
1326
+ }}
1327
+ >
1328
+ {node.locked ? (
1329
+ <IconLock className="size-3" />
1330
+ ) : (
1331
+ <IconLockOpen className="size-3" />
1332
+ )}
1333
+ </Button>
1334
+ </TooltipTrigger>
1335
+ <TooltipContent>
1336
+ {node.locked ? labels.unlock : labels.lock}
1337
+ </TooltipContent>
1338
+ </Tooltip>
1339
+ ) : null}
1340
+
1341
+ {hideable ? (
1342
+ <Tooltip>
1343
+ <TooltipTrigger asChild>
1344
+ <Button
1345
+ type="button"
1346
+ variant="ghost"
1347
+ size="icon"
1348
+ className={cn(
1349
+ "size-5 shrink-0 rounded-sm p-0 text-muted-foreground opacity-0 hover:bg-transparent hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100",
1350
+ node.hidden && "opacity-100",
1351
+ isSelected && "text-foreground",
1352
+ )}
1353
+ aria-label={node.hidden ? labels.show : labels.hide}
1354
+ onClick={(event) => {
1355
+ event.stopPropagation();
1356
+ onToggleHidden?.(node.id, !node.hidden);
1357
+ }}
1358
+ >
1359
+ {node.hidden ? (
1360
+ <IconEyeOff className="size-3" />
1361
+ ) : (
1362
+ <IconEye className="size-3" />
1363
+ )}
1364
+ </Button>
1365
+ </TooltipTrigger>
1366
+ <TooltipContent>
1367
+ {node.hidden ? labels.show : labels.hide}
1368
+ </TooltipContent>
1369
+ </Tooltip>
1370
+ ) : null}
1371
+ </div>
1372
+ </div>
1373
+ </ContextMenuTrigger>
1374
+ {showContextMenu ? (
1375
+ <ContextMenuContent className="z-[300] min-w-[160px] text-[12px]">
1376
+ {onRename && node.renamable !== false ? (
1377
+ <ContextMenuItem
1378
+ className="gap-2 text-[12px]"
1379
+ onSelect={() => onStartRename(node)}
1380
+ >
1381
+ <IconPencil className="size-3.5 text-muted-foreground" />
1382
+ {labels.rename}
1383
+ </ContextMenuItem>
1384
+ ) : null}
1385
+ {onRename && node.renamable !== false && (lockable || hideable) ? (
1386
+ <ContextMenuSeparator />
1387
+ ) : null}
1388
+ {lockable ? (
1389
+ <ContextMenuItem
1390
+ className="gap-2 text-[12px]"
1391
+ onSelect={() => onToggleLocked?.(node.id, !node.locked)}
1392
+ >
1393
+ {node.locked ? (
1394
+ <IconLockOpen className="size-3.5 text-muted-foreground" />
1395
+ ) : (
1396
+ <IconLock className="size-3.5 text-muted-foreground" />
1397
+ )}
1398
+ {node.locked ? labels.unlock : labels.lock}
1399
+ </ContextMenuItem>
1400
+ ) : null}
1401
+ {hideable ? (
1402
+ <ContextMenuItem
1403
+ className="gap-2 text-[12px]"
1404
+ onSelect={() => onToggleHidden?.(node.id, !node.hidden)}
1405
+ >
1406
+ {node.hidden ? (
1407
+ <IconEye className="size-3.5 text-muted-foreground" />
1408
+ ) : (
1409
+ <IconEyeOff className="size-3.5 text-muted-foreground" />
1410
+ )}
1216
1411
  {node.hidden ? labels.show : labels.hide}
1217
- </TooltipContent>
1218
- </Tooltip>
1219
- ) : null}
1220
- </div>
1221
- </div>
1412
+ </ContextMenuItem>
1413
+ ) : null}
1414
+ </ContextMenuContent>
1415
+ ) : null}
1416
+ </ContextMenu>
1222
1417
  );
1223
1418
  }
1224
1419
 
@@ -1580,6 +1775,18 @@ function ElementLayerGlyph({ className }: { className?: string }) {
1580
1775
  );
1581
1776
  }
1582
1777
 
1778
+ function layerCanDropInside(node: LayersPanelNode, hasChildren: boolean) {
1779
+ return (
1780
+ hasChildren ||
1781
+ Boolean(node.layout?.isFlexContainer || node.layout?.isGridContainer) ||
1782
+ node.type === "file" ||
1783
+ node.type === "screen" ||
1784
+ node.type === "frame" ||
1785
+ node.type === "group" ||
1786
+ node.type === "section"
1787
+ );
1788
+ }
1789
+
1583
1790
  function dropPlacementForEvent(
1584
1791
  event: DragEvent<HTMLDivElement>,
1585
1792
  canDropInside: boolean,