@agent-native/core 0.80.8 → 0.80.10

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 (359) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +19 -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/design-connect.ts +28 -2
  16. package/corpus/core/src/cli/mcp.ts +10 -0
  17. package/corpus/core/src/cli/skills.ts +103 -36
  18. package/corpus/core/src/client/error-format.ts +25 -0
  19. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +13 -11
  20. package/corpus/core/src/client/guided-questions.tsx +32 -4
  21. package/corpus/core/src/client/onboarding/OnboardingPanel.tsx +33 -1
  22. package/corpus/core/src/client/resources/BuiltinCapabilityDetail.tsx +8 -0
  23. package/corpus/core/src/client/resources/use-builtin-capabilities.ts +4 -2
  24. package/corpus/core/src/client/session-replay.ts +34 -0
  25. package/corpus/core/src/client/sharing/ShareButton.tsx +106 -21
  26. package/corpus/core/src/coding-tools/index.ts +1 -1
  27. package/corpus/core/src/mcp/index.ts +2 -0
  28. package/corpus/core/src/mcp/screen-memory-stdio.ts +290 -0
  29. package/corpus/core/src/mcp-client/builtin-capabilities.ts +13 -1
  30. package/corpus/core/src/mcp-client/index.ts +9 -0
  31. package/corpus/core/src/mcp-client/screen-memory-local.ts +461 -0
  32. package/corpus/core/src/onboarding/types.ts +7 -0
  33. package/corpus/core/src/server/agent-chat-plugin.ts +25 -0
  34. package/corpus/core/src/server/agents-bundle.ts +35 -7
  35. package/corpus/core/src/styles/agent-native.css +5 -0
  36. package/corpus/core/src/templates/default/app/global.css +35 -35
  37. package/corpus/templates/analytics/AGENTS.md +6 -0
  38. package/corpus/templates/analytics/actions/github-repo-files.ts +9 -0
  39. package/corpus/templates/analytics/app/components/layout/Layout.tsx +4 -4
  40. package/corpus/templates/analytics/app/global.css +144 -40
  41. package/corpus/templates/analytics/app/pages/adhoc/explorer/index.tsx +1 -1
  42. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +1 -1
  43. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +1 -1
  44. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +4 -4
  45. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +9 -9
  46. package/corpus/templates/analytics/changelog/2026-06-29-agents-can-search-and-read-connected-github-repositories-whe.md +6 -0
  47. package/corpus/templates/analytics/changelog/2026-06-29-sessions-and-explorer-layouts-adapt-when-the-agent-sidebar-is.md +6 -0
  48. package/corpus/templates/assets/app/components/layout/Layout.tsx +3 -1
  49. package/corpus/templates/assets/app/components/library/LibraryPresetGrid.tsx +1 -1
  50. package/corpus/templates/assets/app/global.css +99 -35
  51. package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
  52. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +4 -4
  53. package/corpus/templates/assets/app/routes/library.tsx +4 -4
  54. package/corpus/templates/assets/changelog/2026-06-29-asset-library-and-detail-layouts-adapt-when-the-agent-sidebar.md +6 -0
  55. package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -1
  56. package/corpus/templates/brain/app/global.css +63 -35
  57. package/corpus/templates/brain/app/routes/search.tsx +1 -1
  58. package/corpus/templates/brain/app/routes/settings.tsx +1 -1
  59. package/corpus/templates/brain/app/routes/sources.tsx +2 -2
  60. package/corpus/templates/brain/changelog/2026-06-29-search-settings-and-source-layouts-adapt-when-the-agent-sidebar.md +6 -0
  61. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +4 -4
  62. package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +3 -2
  63. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +4 -2
  64. package/corpus/templates/calendar/app/global.css +69 -35
  65. package/corpus/templates/calendar/changelog/2026-06-29-event-and-find-time-layouts-adapt-when-the-agent-sidebar-is.md +6 -0
  66. package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -1
  67. package/corpus/templates/chat/app/global.css +35 -35
  68. package/corpus/templates/chat/changelog/2026-06-29-chat-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  69. package/corpus/templates/clips/AGENTS.md +13 -0
  70. package/corpus/templates/clips/actions/get-screen-memory-status.ts +12 -0
  71. package/corpus/templates/clips/actions/query-screen-memory-context.ts +29 -0
  72. package/corpus/templates/clips/actions/view-screen.ts +2 -2
  73. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  74. package/corpus/templates/clips/app/global.css +47 -35
  75. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +3 -3
  76. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +45 -18
  77. package/corpus/templates/clips/changelog/2026-06-29-meeting-detail-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  78. package/corpus/templates/clips/changelog/2026-06-29-screen-memory-keeps-a-local-rolling-screen-buffer-for-agent-context.md +5 -0
  79. package/corpus/templates/clips/changelog/2026-06-29-video-chat-is-easier-to-find-on-recording-pages-and-s3-setup.md +6 -0
  80. package/corpus/templates/clips/chrome-extension/src/styles.css +39 -39
  81. package/corpus/templates/clips/desktop/src/app.tsx +290 -1
  82. package/corpus/templates/clips/desktop/src/shared/config.ts +83 -0
  83. package/corpus/templates/clips/desktop/src/styles.css +67 -52
  84. package/corpus/templates/clips/desktop/src-tauri/src/config.rs +50 -0
  85. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +17 -0
  86. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +13 -13
  87. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +990 -0
  88. package/corpus/templates/clips/server/plugins/onboarding.ts +2 -0
  89. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +3 -3
  90. package/corpus/templates/content/app/components/layout/Layout.tsx +1 -1
  91. package/corpus/templates/content/app/global.css +29 -15
  92. package/corpus/templates/content/changelog/2026-06-29-database-gallery-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  93. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +25 -20
  94. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -0
  95. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +8 -0
  96. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +13 -0
  97. package/corpus/templates/design/AGENTS.md +20 -12
  98. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1054 -0
  99. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +717 -0
  100. package/corpus/templates/design/actions/add-breakpoint.ts +143 -0
  101. package/corpus/templates/design/actions/add-localhost-screens.ts +5 -0
  102. package/corpus/templates/design/actions/apply-a11y-fix.ts +317 -0
  103. package/corpus/templates/design/actions/apply-component-prop-edit.ts +433 -0
  104. package/corpus/templates/design/actions/apply-design-state.ts +200 -0
  105. package/corpus/templates/design/actions/apply-design-token-edit.ts +202 -0
  106. package/corpus/templates/design/actions/apply-motion-edit.ts +450 -0
  107. package/corpus/templates/design/actions/apply-shader-fill.ts +404 -0
  108. package/corpus/templates/design/actions/apply-visual-edit.ts +214 -8
  109. package/corpus/templates/design/actions/capture-design-state.ts +224 -0
  110. package/corpus/templates/design/actions/connect-builder-app.ts +162 -0
  111. package/corpus/templates/design/actions/create-component.ts +447 -0
  112. package/corpus/templates/design/actions/create-design-branch.ts +263 -0
  113. package/corpus/templates/design/actions/create-design-state.ts +162 -0
  114. package/corpus/templates/design/actions/delete-design-state.ts +55 -0
  115. package/corpus/templates/design/actions/deploy-design-preview.ts +275 -0
  116. package/corpus/templates/design/actions/export-coding-handoff.ts +12 -4
  117. package/corpus/templates/design/actions/generate-design.ts +6 -21
  118. package/corpus/templates/design/actions/generate-screens.ts +43 -2
  119. package/corpus/templates/design/actions/get-component-details.ts +242 -0
  120. package/corpus/templates/design/actions/get-design-branch-diff.ts +362 -0
  121. package/corpus/templates/design/actions/get-design-review.ts +314 -0
  122. package/corpus/templates/design/actions/get-design-surface-index.ts +582 -0
  123. package/corpus/templates/design/actions/get-design.ts +1 -0
  124. package/corpus/templates/design/actions/get-motion-timeline.ts +99 -0
  125. package/corpus/templates/design/actions/index-components.ts +258 -0
  126. package/corpus/templates/design/actions/index-design-tokens.ts +273 -0
  127. package/corpus/templates/design/actions/list-design-extensions.ts +309 -0
  128. package/corpus/templates/design/actions/list-design-source-capabilities.ts +153 -0
  129. package/corpus/templates/design/actions/list-design-states.ts +72 -0
  130. package/corpus/templates/design/actions/migrate-inline-design-to-app.ts +298 -0
  131. package/corpus/templates/design/actions/open-component-source.ts +237 -0
  132. package/corpus/templates/design/actions/present-design-variants.ts +355 -30
  133. package/corpus/templates/design/actions/preview-component-prop-edit.ts +254 -0
  134. package/corpus/templates/design/actions/preview-design-token-edit.ts +113 -0
  135. package/corpus/templates/design/actions/preview-shader-fill.ts +189 -0
  136. package/corpus/templates/design/actions/remove-breakpoint.ts +103 -0
  137. package/corpus/templates/design/actions/remove-motion-timeline.ts +196 -0
  138. package/corpus/templates/design/actions/run-design-audit.ts +421 -0
  139. package/corpus/templates/design/actions/run-design-extension-action.ts +284 -0
  140. package/corpus/templates/design/actions/set-active-breakpoint.ts +39 -0
  141. package/corpus/templates/design/actions/view-screen.ts +75 -31
  142. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1738 -135
  143. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +1 -1
  144. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +632 -43
  145. package/corpus/templates/design/app/components/design/DrawOverlay.tsx +1 -1
  146. package/corpus/templates/design/app/components/design/EditPanel.tsx +1873 -132
  147. package/corpus/templates/design/app/components/design/LayersPanel.tsx +384 -219
  148. package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +157 -0
  149. package/corpus/templates/design/app/components/design/MotionDock.tsx +1062 -0
  150. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2103 -295
  151. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +83 -206
  152. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +759 -0
  153. package/corpus/templates/design/app/components/design/StatesPanel.tsx +579 -0
  154. package/corpus/templates/design/app/components/design/TokensPanel.tsx +573 -0
  155. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +1 -1
  156. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +241 -0
  157. package/corpus/templates/design/app/components/design/index.ts +16 -1
  158. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +238 -137
  159. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +188 -14
  160. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +24 -5
  161. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +57 -1
  162. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +145 -0
  163. package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +42 -12
  164. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +37 -12
  165. package/corpus/templates/design/app/components/design/inspector/index.ts +1 -0
  166. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +12 -5
  167. package/corpus/templates/design/app/components/design/types.ts +9 -0
  168. package/corpus/templates/design/app/components/layout/Layout.tsx +23 -5
  169. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
  170. package/corpus/templates/design/app/global.css +77 -49
  171. package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
  172. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +131 -0
  173. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +11 -2
  174. package/corpus/templates/design/app/i18n/zh-TW.ts +34 -0
  175. package/corpus/templates/design/app/i18n-data.ts +314 -3
  176. package/corpus/templates/design/app/pages/DesignEditor.tsx +4929 -892
  177. package/corpus/templates/design/app/root.tsx +1 -1
  178. package/corpus/templates/design/changelog/2026-06-29-added-a-review-panel-in-the-design-editor-s-inspector-with-a.md +6 -0
  179. package/corpus/templates/design/changelog/2026-06-29-containers-no-longer-block-selecting-nested-elements-and-nor.md +6 -0
  180. package/corpus/templates/design/changelog/2026-06-29-copying-and-pasting-layers-no-longer-shows-success-notificat.md +6 -0
  181. package/corpus/templates/design/changelog/2026-06-29-design-agents-now-generate-and-refine-variants-from-stronger.md +6 -0
  182. package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +6 -0
  183. package/corpus/templates/design/changelog/2026-06-29-design-questions-now-appear-as-a-cleaner-canvas-intake-with-.md +6 -0
  184. package/corpus/templates/design/changelog/2026-06-29-design-variant-directions-now-appear-as-board-screens-with-chat-buttons.md +5 -0
  185. package/corpus/templates/design/changelog/2026-06-29-device-presets-in-all-screens-view-resize-the-selected-previ.md +6 -0
  186. package/corpus/templates/design/changelog/2026-06-29-element-selection-no-longer-snaps-back-to-the-previous-layer.md +6 -0
  187. package/corpus/templates/design/changelog/2026-06-29-frame-resize-measurements-now-stay-beside-the-cursor-while-d.md +6 -0
  188. package/corpus/templates/design/changelog/2026-06-29-inspector-number-fields-have-cleaner-tooltips-and-draggable-.md +6 -0
  189. package/corpus/templates/design/changelog/2026-06-29-layer-and-canvas-drags-keep-the-layer-list-stable-while-chan.md +6 -0
  190. package/corpus/templates/design/changelog/2026-06-29-layer-locking-layer-nesting-generated-question-intake-and-im.md +6 -0
  191. package/corpus/templates/design/changelog/2026-06-29-layer-moves-can-be-undone-and-redone-without-flashing-the-ca.md +6 -0
  192. package/corpus/templates/design/changelog/2026-06-29-layer-names-in-the-sidebar-can-scroll-horizontally-instead-o.md +6 -0
  193. package/corpus/templates/design/changelog/2026-06-29-layer-nesting-in-the-sidebar-now-uses-tighter-spacing-so-nam.md +6 -0
  194. package/corpus/templates/design/changelog/2026-06-29-layers-no-longer-repeat-document-wrapper-rows-when-a-screen-.md +6 -0
  195. package/corpus/templates/design/changelog/2026-06-29-overview-previews-now-keep-element-hover-and-selection-tied-.md +6 -0
  196. package/corpus/templates/design/changelog/2026-06-29-pressing-escape-now-cancels-a-stuck-screen-drag-in-the-overv.md +6 -0
  197. package/corpus/templates/design/changelog/2026-06-29-screen-overview-is-easier-to-find-and-named-screens-open-dir.md +6 -0
  198. package/corpus/templates/design/changelog/2026-06-29-screen-overview-only-highlights-screens-when-the-frame-or-ti.md +6 -0
  199. package/corpus/templates/design/changelog/2026-06-29-screen-overview-previews-now-resize-to-match-the-selected-de.md +6 -0
  200. package/corpus/templates/design/changelog/2026-06-29-screen-overview-selection-now-uses-a-pointer-cursor-instead-.md +6 -0
  201. package/corpus/templates/design/changelog/2026-06-29-screen-overview-selection-outlines-now-stay-locked-to-the-se.md +6 -0
  202. package/corpus/templates/design/changelog/2026-06-29-screen-overview-zoom-now-reports-real-screen-scale-separatel.md +6 -0
  203. package/corpus/templates/design/changelog/2026-06-29-screen-overview-zooming-feels-smoother-and-selection-outline.md +6 -0
  204. package/corpus/templates/design/changelog/2026-06-29-screen-overview-zooming-now-stays-on-the-overview-canvas-ins.md +6 -0
  205. package/corpus/templates/design/changelog/2026-06-29-screen-previews-use-a-single-blue-hover-border-in-all-screen.md +6 -0
  206. package/corpus/templates/design/changelog/2026-06-29-selected-containers-show-draggable-padding-and-gap-guides-on.md +6 -0
  207. package/corpus/templates/design/changelog/2026-06-29-selecting-a-nested-layer-from-all-screens-now-keeps-you-in-a.md +6 -0
  208. package/corpus/templates/design/changelog/2026-06-29-share-now-groups-link-sharing-exports-and-agent-handoff.md +6 -0
  209. package/corpus/templates/design/changelog/2026-06-29-the-design-editor-adds-a-studio-layer-with-tokens-respon.md +6 -0
  210. package/corpus/templates/design/changelog/2026-06-29-the-design-editor-loading-skeleton-now-uses-a-softer-charcoa.md +6 -0
  211. package/corpus/templates/design/changelog/2026-06-29-the-device-preview-menu-now-sits-next-to-the-preview-button-.md +6 -0
  212. package/corpus/templates/design/changelog/2026-06-29-the-inspector-stays-read-only-while-an-empty-design-is-still.md +6 -0
  213. package/corpus/templates/design/changelog/2026-06-29-the-missing-design-view-now-matches-the-rest-of-the-editor-c.md +6 -0
  214. package/corpus/templates/design/changelog/2026-06-29-the-pen-tool-icon-and-tool-option-menus-better-match-the-edi.md +6 -0
  215. package/corpus/templates/design/changelog/2026-06-29-typography-controls-now-keep-the-font-name-visible-and-move-.md +6 -0
  216. package/corpus/templates/design/changelog/2026-06-30-accessibility-findings-now-offer-a-one-click-fix.md +6 -0
  217. package/corpus/templates/design/changelog/2026-06-30-component-instances-now-have-editable-props-in-the-inspe.md +6 -0
  218. package/corpus/templates/design/changelog/2026-06-30-inspect-code-now-shows-the-elements-opening-tag-at-a-gla.md +6 -0
  219. package/corpus/templates/design/changelog/2026-06-30-shader-fill-presets-can-now-be-applied-to-an-element.md +6 -0
  220. package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-can-now-add-a-track-to-any-element-a.md +6 -0
  221. package/corpus/templates/design/changelog/2026-06-30-visual-editor-selection-layer-paint-and-drawing-controls-are-more-reliable.md +6 -0
  222. package/corpus/templates/design/e2e/README.md +3 -3
  223. package/corpus/templates/design/e2e/global-setup.ts +11 -22
  224. package/corpus/templates/design/e2e/helpers.ts +128 -11
  225. package/corpus/templates/design/playwright.config.ts +9 -3
  226. package/corpus/templates/design/server/db/schema.ts +123 -0
  227. package/corpus/templates/design/server/lib/coding-handoff.ts +126 -2
  228. package/corpus/templates/design/server/plugins/core-routes.ts +1 -2
  229. package/corpus/templates/design/server/plugins/db.ts +90 -0
  230. package/corpus/templates/design/server/routes/api/design-handoff/[id].zip.get.ts +86 -0
  231. package/corpus/templates/design/shared/builder-app.ts +297 -0
  232. package/corpus/templates/design/shared/capability-resolver.ts +123 -0
  233. package/corpus/templates/design/shared/capture-sanitize.ts +70 -0
  234. package/corpus/templates/design/shared/code-layer.ts +1141 -93
  235. package/corpus/templates/design/shared/component-model.ts +239 -0
  236. package/corpus/templates/design/shared/design-review.ts +275 -0
  237. package/corpus/templates/design/shared/design-source-capabilities.ts +286 -0
  238. package/corpus/templates/design/shared/design-state.ts +112 -0
  239. package/corpus/templates/design/shared/design-surface-index.ts +258 -0
  240. package/corpus/templates/design/shared/generation-session.ts +41 -0
  241. package/corpus/templates/design/shared/motion-compiler.ts +278 -0
  242. package/corpus/templates/design/shared/motion-timeline.ts +193 -0
  243. package/corpus/templates/design/shared/responsive-classes.ts +452 -0
  244. package/corpus/templates/design/shared/shader-fill.ts +323 -0
  245. package/corpus/templates/design/shared/source-mode.ts +245 -0
  246. package/corpus/templates/dispatch/app/global.css +79 -35
  247. package/corpus/templates/dispatch/app/routes/integrations.tsx +8 -7
  248. package/corpus/templates/dispatch/changelog/2026-06-29-integration-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  249. package/corpus/templates/forms/app/components/layout/Layout.tsx +6 -2
  250. package/corpus/templates/forms/app/global.css +64 -35
  251. package/corpus/templates/forms/app/pages/FormsListPage.tsx +2 -2
  252. package/corpus/templates/forms/app/pages/ResponseInsightsPage.tsx +3 -3
  253. package/corpus/templates/forms/changelog/2026-06-29-form-list-and-insight-layouts-adapt-when-the-agent-sidebar.md +6 -0
  254. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +3 -1
  255. package/corpus/templates/macros/app/global.css +25 -9
  256. package/corpus/templates/macros/changelog/2026-06-29-macro-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  257. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +6 -2
  258. package/corpus/templates/mail/app/global.css +42 -36
  259. package/corpus/templates/mail/app/pages/InboxPage.tsx +1 -1
  260. package/corpus/templates/mail/changelog/2026-06-29-the-contact-panel-now-adapts-to-the-available-mail-pane-width.md +6 -0
  261. package/corpus/templates/plan/app/components/layout/Layout.tsx +3 -1
  262. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +6 -2
  263. package/corpus/templates/plan/app/components/plan/DocumentArea.tsx +6 -7
  264. package/corpus/templates/plan/app/global.css +74 -42
  265. package/corpus/templates/plan/changelog/2026-06-29-plan-canvases-open-without-an-initial-pan-and-zoom-flicker.md +6 -0
  266. package/corpus/templates/plan/changelog/2026-06-29-plan-document-blocks-adapt-to-the-available-document-width.md +6 -0
  267. package/corpus/templates/plan/changelog/2026-06-29-plan-titles-and-summaries-can-be-edited-inline-without-focus.md +6 -0
  268. package/corpus/templates/slides/app/components/layout/Layout.tsx +3 -1
  269. package/corpus/templates/slides/app/global.css +49 -33
  270. package/corpus/templates/slides/changelog/2026-06-29-slide-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  271. package/corpus/templates/videos/app/components/layout/Layout.tsx +3 -1
  272. package/corpus/templates/videos/app/global.css +35 -35
  273. package/corpus/templates/videos/changelog/2026-06-29-video-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  274. package/dist/cli/design-connect.d.ts.map +1 -1
  275. package/dist/cli/design-connect.js +28 -2
  276. package/dist/cli/design-connect.js.map +1 -1
  277. package/dist/cli/mcp.d.ts.map +1 -1
  278. package/dist/cli/mcp.js +10 -0
  279. package/dist/cli/mcp.js.map +1 -1
  280. package/dist/cli/skills.d.ts.map +1 -1
  281. package/dist/cli/skills.js +95 -33
  282. package/dist/cli/skills.js.map +1 -1
  283. package/dist/client/error-format.d.ts.map +1 -1
  284. package/dist/client/error-format.js +17 -0
  285. package/dist/client/error-format.js.map +1 -1
  286. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  287. package/dist/client/extensions/ExtensionViewer.js +3 -4
  288. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  289. package/dist/client/guided-questions.d.ts +4 -0
  290. package/dist/client/guided-questions.d.ts.map +1 -1
  291. package/dist/client/guided-questions.js +16 -7
  292. package/dist/client/guided-questions.js.map +1 -1
  293. package/dist/client/onboarding/OnboardingPanel.js +26 -1
  294. package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
  295. package/dist/client/resources/BuiltinCapabilityDetail.d.ts.map +1 -1
  296. package/dist/client/resources/BuiltinCapabilityDetail.js +1 -1
  297. package/dist/client/resources/BuiltinCapabilityDetail.js.map +1 -1
  298. package/dist/client/resources/use-builtin-capabilities.d.ts +1 -1
  299. package/dist/client/resources/use-builtin-capabilities.d.ts.map +1 -1
  300. package/dist/client/resources/use-builtin-capabilities.js +2 -1
  301. package/dist/client/resources/use-builtin-capabilities.js.map +1 -1
  302. package/dist/client/session-replay.d.ts.map +1 -1
  303. package/dist/client/session-replay.js +31 -0
  304. package/dist/client/session-replay.js.map +1 -1
  305. package/dist/client/sharing/ShareButton.d.ts +14 -0
  306. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  307. package/dist/client/sharing/ShareButton.js +36 -7
  308. package/dist/client/sharing/ShareButton.js.map +1 -1
  309. package/dist/coding-tools/index.js +1 -1
  310. package/dist/coding-tools/index.js.map +1 -1
  311. package/dist/mcp/index.d.ts +2 -0
  312. package/dist/mcp/index.d.ts.map +1 -1
  313. package/dist/mcp/index.js +1 -0
  314. package/dist/mcp/index.js.map +1 -1
  315. package/dist/mcp/screen-memory-stdio.d.ts +6 -0
  316. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -0
  317. package/dist/mcp/screen-memory-stdio.js +213 -0
  318. package/dist/mcp/screen-memory-stdio.js.map +1 -0
  319. package/dist/mcp-client/builtin-capabilities.d.ts +1 -1
  320. package/dist/mcp-client/builtin-capabilities.d.ts.map +1 -1
  321. package/dist/mcp-client/builtin-capabilities.js +10 -0
  322. package/dist/mcp-client/builtin-capabilities.js.map +1 -1
  323. package/dist/mcp-client/index.d.ts +1 -0
  324. package/dist/mcp-client/index.d.ts.map +1 -1
  325. package/dist/mcp-client/index.js +1 -0
  326. package/dist/mcp-client/index.js.map +1 -1
  327. package/dist/mcp-client/screen-memory-local.d.ts +61 -0
  328. package/dist/mcp-client/screen-memory-local.d.ts.map +1 -0
  329. package/dist/mcp-client/screen-memory-local.js +336 -0
  330. package/dist/mcp-client/screen-memory-local.js.map +1 -0
  331. package/dist/observability/routes.d.ts +3 -3
  332. package/dist/onboarding/types.d.ts +7 -0
  333. package/dist/onboarding/types.d.ts.map +1 -1
  334. package/dist/onboarding/types.js.map +1 -1
  335. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  336. package/dist/server/agent-chat-plugin.js +26 -0
  337. package/dist/server/agent-chat-plugin.js.map +1 -1
  338. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  339. package/dist/server/agents-bundle.d.ts.map +1 -1
  340. package/dist/server/agents-bundle.js +22 -7
  341. package/dist/server/agents-bundle.js.map +1 -1
  342. package/dist/styles/agent-native.css +5 -0
  343. package/dist/templates/default/app/global.css +35 -35
  344. package/docs/content/locales/ar-SA/template-design.mdx +6 -0
  345. package/docs/content/locales/de-DE/template-design.mdx +8 -0
  346. package/docs/content/locales/es-ES/template-design.mdx +10 -2
  347. package/docs/content/locales/fr-FR/template-design.mdx +10 -2
  348. package/docs/content/locales/hi-IN/template-design.mdx +7 -0
  349. package/docs/content/locales/ja-JP/template-design.mdx +7 -0
  350. package/docs/content/locales/ko-KR/template-design.mdx +7 -0
  351. package/docs/content/locales/pt-BR/template-design.mdx +9 -2
  352. package/docs/content/locales/zh-CN/template-design.mdx +6 -0
  353. package/docs/content/locales/zh-TW/template-design.mdx +6 -0
  354. package/docs/content/template-design.mdx +9 -2
  355. package/package.json +1 -1
  356. package/src/templates/default/app/global.css +35 -35
  357. package/corpus/templates/design/app/components/design/VariantGrid.tsx +0 -189
  358. package/corpus/templates/design/app/components/design/VariantHandoffCard.tsx +0 -115
  359. package/corpus/templates/design/app/hooks/use-variant-flow.ts +0 -342
@@ -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[];
@@ -185,6 +200,7 @@ const SECTION_ELEMENT_ID = "__design_layers_elements__";
185
200
  // Module-level drag state: dataTransfer.getData() returns "" during dragover
186
201
  // per spec; the source row stores the drag payload here on dragstart instead.
187
202
  let activeDragState: { sourceId: string; draggedIds: string[] } | null = null;
203
+ let activeDropIntent: LayersPanelMoveIntent | null = null;
188
204
 
189
205
  const ROW_BASE_INDENT = 4;
190
206
  const ROW_INDENT_STEP = 28;
@@ -198,6 +214,7 @@ function defaultLabels(t: ReturnType<typeof useT>): LayersPanelLabels {
198
214
  return {
199
215
  title: t("layersPanel.title"),
200
216
  screens: t("layersPanel.screens"),
217
+ allScreens: t("layersPanel.allScreens"),
201
218
  screenOverview: t("designEditor.screenOverview"),
202
219
  addScreen: t("layersPanel.addScreen"),
203
220
  searchPlaceholder: t("layersPanel.searchPlaceholder"),
@@ -406,6 +423,7 @@ function layerCanShowBadge(node: LayersPanelNode) {
406
423
  export function LayersPanel({
407
424
  screens,
408
425
  activeScreenId,
426
+ screenOverviewActive = false,
409
427
  files,
410
428
  layers,
411
429
  codeLayers,
@@ -432,6 +450,7 @@ export function LayersPanel({
432
450
  const t = useT();
433
451
  const labels = useMemo(() => mergeLabels(labelsProp, t), [labelsProp, t]);
434
452
  const selectedIdSet = useMemo(() => new Set(selectedIds), [selectedIds]);
453
+ const selectedIdsRef = useRef<readonly string[]>(selectedIds);
435
454
  const expandedIdSet = useMemo(() => new Set(expandedIds), [expandedIds]);
436
455
  const lastSelectionAnchorRef = useRef<string | null>(selectedIds[0] ?? null);
437
456
  const searchInputRef = useRef<HTMLInputElement>(null);
@@ -467,6 +486,10 @@ export function LayersPanel({
467
486
  [roots, selectedIdSet],
468
487
  );
469
488
 
489
+ useLayoutEffect(() => {
490
+ selectedIdsRef.current = selectedIds;
491
+ }, [selectedIds]);
492
+
470
493
  useEffect(() => {
471
494
  if (selectedAncestorIds.length === 0) return;
472
495
  const next = new Set(expandedIds);
@@ -513,10 +536,14 @@ export function LayersPanel({
513
536
  id: string,
514
537
  options: {
515
538
  additive: boolean;
539
+ currentSelectedIds?: string[];
516
540
  range: boolean;
517
541
  source: "keyboard" | "pointer";
518
542
  },
519
543
  ) => {
544
+ const currentSelectedIds =
545
+ options.currentSelectedIds ?? selectedIdsRef.current;
546
+ const currentSelectedIdSet = new Set(currentSelectedIds);
520
547
  let nextIds: string[];
521
548
  if (options.range && lastSelectionAnchorRef.current) {
522
549
  let anchor = lastSelectionAnchorRef.current;
@@ -538,19 +565,18 @@ export function LayersPanel({
538
565
  const [start, end] = from < to ? [from, to] : [to, from];
539
566
  const rangeIds = selectableVisibleIds.slice(start, end + 1);
540
567
  nextIds = options.additive
541
- ? Array.from(new Set([...selectedIds, ...rangeIds]))
568
+ ? Array.from(new Set([...currentSelectedIds, ...rangeIds]))
542
569
  : rangeIds;
543
570
  } else {
544
571
  nextIds = [id];
545
572
  }
546
573
  } else if (options.additive) {
547
- nextIds = selectedIdSet.has(id)
548
- ? selectedIds.filter((selectedId) => selectedId !== id)
549
- : [...selectedIds, id];
574
+ nextIds = currentSelectedIdSet.has(id)
575
+ ? currentSelectedIds.filter((selectedId) => selectedId !== id)
576
+ : [...currentSelectedIds, id];
550
577
  } else {
551
578
  nextIds = [id];
552
579
  }
553
-
554
580
  // Only advance the anchor on plain clicks; Shift+clicks extend from the
555
581
  // existing anchor so the pivot stays fixed across consecutive range clicks.
556
582
  if (!options.range) {
@@ -558,7 +584,7 @@ export function LayersPanel({
558
584
  }
559
585
  onSelectionChange(nextIds, { id, selectedIds: nextIds, ...options });
560
586
  },
561
- [onSelectionChange, selectableVisibleIds, selectedIdSet, selectedIds],
587
+ [onSelectionChange, selectableVisibleIds],
562
588
  );
563
589
 
564
590
  const commitRename = useCallback(
@@ -642,12 +668,6 @@ export function LayersPanel({
642
668
  {labels.screens}
643
669
  </h2>
644
670
  <div className="flex items-center gap-0.5 text-muted-foreground">
645
- <IconTooltipButton
646
- label={labels.screenOverview}
647
- onClick={onScreenOverview}
648
- >
649
- <IconLayoutGrid className="size-3.5" />
650
- </IconTooltipButton>
651
671
  <IconTooltipButton
652
672
  label={labels.searchPlaceholder}
653
673
  onClick={openSearch}
@@ -663,9 +683,30 @@ export function LayersPanel({
663
683
  </IconTooltipButton>
664
684
  </div>
665
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)]" />
666
706
  <div className="space-y-0.5 px-2">
667
707
  {screenRows.map((screen) => {
668
- const isActive = screen.id === activeScreenId;
708
+ const isActive =
709
+ !screenOverviewActive && screen.id === activeScreenId;
669
710
  return (
670
711
  <button
671
712
  key={screen.id}
@@ -676,6 +717,7 @@ export function LayersPanel({
676
717
  ? "bg-[var(--design-editor-active-row-color)] text-foreground"
677
718
  : "text-foreground/85 hover:bg-[var(--design-editor-active-row-color)] hover:text-foreground",
678
719
  )}
720
+ aria-current={isActive ? "page" : undefined}
679
721
  onClick={() => onScreenSelect?.(screen.id)}
680
722
  title={screen.filename ?? screen.name}
681
723
  >
@@ -740,9 +782,13 @@ export function LayersPanel({
740
782
  </div>
741
783
  ) : null}
742
784
 
743
- <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">
744
786
  {visibleRows.length ? (
745
- <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
+ >
746
792
  {visibleRows.map((row) => (
747
793
  <LayerRow
748
794
  key={row.rowKey}
@@ -766,6 +812,7 @@ export function LayersPanel({
766
812
  onCommitRename={commitRename}
767
813
  onCancelRename={() => setRenamingId(null)}
768
814
  onStartRename={startRename}
815
+ onRename={onRename}
769
816
  onSelect={selectNode}
770
817
  onToggleExpanded={(expanded) =>
771
818
  onExpandedIdsChange(
@@ -809,6 +856,7 @@ function LayerRow({
809
856
  onCommitRename,
810
857
  onCancelRename,
811
858
  onStartRename,
859
+ onRename,
812
860
  onSelect,
813
861
  onToggleExpanded,
814
862
  onToggleLocked,
@@ -835,10 +883,12 @@ function LayerRow({
835
883
  onCommitRename: (id: string) => void;
836
884
  onCancelRename: () => void;
837
885
  onStartRename: (node: LayersPanelNode) => void;
886
+ onRename?: (id: string, name: string) => void;
838
887
  onSelect: (
839
888
  id: string,
840
889
  options: {
841
890
  additive: boolean;
891
+ currentSelectedIds?: string[];
842
892
  range: boolean;
843
893
  source: "keyboard" | "pointer";
844
894
  },
@@ -859,17 +909,39 @@ function LayerRow({
859
909
  const selectable = node.selectable !== false;
860
910
  const lockable = node.lockable !== false && Boolean(onToggleLocked);
861
911
  const hideable = node.hideable !== false && Boolean(onToggleHidden);
862
- const draggable = selectable && Boolean(onMoveLayer);
912
+ const dragEligible = selectable && !node.locked && !node.hidden;
913
+ const draggable = dragEligible && Boolean(onMoveLayer);
914
+ const canDropInside = layerCanDropInside(node, hasChildren);
863
915
  const activeDrop =
864
916
  dropIndicator?.targetId === node.id ? dropIndicator.placement : null;
865
917
  // Tracks whether the user pressed Escape to cancel rename so that the
866
918
  // subsequent blur event does not commit the edit.
867
919
  const renameCancelledRef = useRef(false);
868
920
 
921
+ const readSelectedIdsFromTree = (target: HTMLElement): string[] => {
922
+ const tree = target.closest('[role="tree"]');
923
+ if (!tree) return selectedIds.filter((id) => !id.startsWith("__"));
924
+ return Array.from(
925
+ tree.querySelectorAll<HTMLElement>(
926
+ '[role="treeitem"][aria-selected="true"] [data-layer-row-button][data-layer-node-id]',
927
+ ),
928
+ )
929
+ .map((button) => button.dataset.layerNodeId)
930
+ .filter((id): id is string => Boolean(id && !id.startsWith("__")));
931
+ };
932
+
869
933
  const handlePointerSelect = (event: MouseEvent<HTMLButtonElement>) => {
870
934
  if (!selectable) return;
935
+ if (event.detail === 0) return;
936
+ const nativeEvent = event.nativeEvent;
937
+ const additive =
938
+ event.metaKey ||
939
+ event.ctrlKey ||
940
+ nativeEvent.metaKey ||
941
+ nativeEvent.ctrlKey;
871
942
  onSelect(node.id, {
872
- additive: event.metaKey || event.ctrlKey,
943
+ additive,
944
+ currentSelectedIds: readSelectedIdsFromTree(event.currentTarget),
873
945
  range: event.shiftKey,
874
946
  source: "pointer",
875
947
  });
@@ -900,11 +972,12 @@ function LayerRow({
900
972
  (visibleRow) => visibleRow.rowKey === row.rowKey,
901
973
  );
902
974
 
903
- if (event.key === "Enter" || event.key === " ") {
975
+ if (event.key === "Enter" || event.key === " " || event.key === "Space") {
904
976
  event.preventDefault();
905
977
  if (!selectable) return;
906
978
  onSelect(node.id, {
907
979
  additive: event.metaKey || event.ctrlKey,
980
+ currentSelectedIds: readSelectedIdsFromTree(event.currentTarget),
908
981
  range: event.shiftKey,
909
982
  source: "keyboard",
910
983
  });
@@ -989,7 +1062,7 @@ function LayerRow({
989
1062
  };
990
1063
 
991
1064
  const handleDragOver = (event: DragEvent<HTMLDivElement>) => {
992
- if (!onMoveLayer || !selectable) return;
1065
+ if (!onMoveLayer || !dragEligible) return;
993
1066
  // dataTransfer.getData() always returns "" during dragover per spec.
994
1067
  // Read from the module-level activeDragState set in handleDragStart instead.
995
1068
  if (!activeDragState) return;
@@ -1002,16 +1075,17 @@ function LayerRow({
1002
1075
  const intent = {
1003
1076
  draggedIds: cleanedIds,
1004
1077
  targetId: node.id,
1005
- placement: dropPlacementForEvent(event, canAcceptChildren),
1078
+ placement: dropPlacementForEvent(event, canDropInside),
1006
1079
  } satisfies LayersPanelMoveIntent;
1007
1080
  if (canMoveLayer && !canMoveLayer(intent)) return;
1008
1081
  event.preventDefault();
1009
1082
  event.dataTransfer.dropEffect = "move";
1083
+ activeDropIntent = intent;
1010
1084
  onDropIndicatorChange(intent);
1011
1085
  };
1012
1086
 
1013
1087
  const handleDrop = (event: DragEvent<HTMLDivElement>) => {
1014
- if (!onMoveLayer || !selectable) {
1088
+ if (!onMoveLayer || !dragEligible) {
1015
1089
  onDropIndicatorChange(null);
1016
1090
  return;
1017
1091
  }
@@ -1035,233 +1109,312 @@ function LayerRow({
1035
1109
  (id) => id && id !== node.id && !id.startsWith("__"),
1036
1110
  );
1037
1111
  if (cleanedIds.length > 0) {
1038
- // Recompute placement from the drop event position rather than relying on
1039
- // React state (dropIndicator) which may be stale or null.
1040
- const intent = {
1041
- draggedIds: cleanedIds,
1042
- targetId: node.id,
1043
- placement: dropPlacementForEvent(event, canAcceptChildren),
1044
- } satisfies LayersPanelMoveIntent;
1112
+ const storedIntent =
1113
+ activeDropIntent?.targetId === node.id
1114
+ ? {
1115
+ ...activeDropIntent,
1116
+ draggedIds: activeDropIntent.draggedIds.filter((id) =>
1117
+ cleanedIds.includes(id),
1118
+ ),
1119
+ }
1120
+ : null;
1121
+ const intent =
1122
+ storedIntent && storedIntent.draggedIds.length > 0
1123
+ ? storedIntent
1124
+ : ({
1125
+ draggedIds: cleanedIds,
1126
+ targetId: node.id,
1127
+ placement: dropPlacementForEvent(event, canDropInside),
1128
+ } satisfies LayersPanelMoveIntent);
1045
1129
  if (!canMoveLayer || canMoveLayer(intent)) {
1046
1130
  onMoveLayer(intent);
1047
1131
  }
1048
1132
  }
1133
+ activeDropIntent = null;
1049
1134
  onDropIndicatorChange(null);
1050
1135
  };
1051
1136
 
1052
- return (
1053
- <div
1054
- ref={rowRef}
1055
- role="treeitem"
1056
- aria-expanded={hasChildren ? isExpanded : undefined}
1057
- aria-level={depth + 1}
1058
- aria-selected={selectable ? isSelected : undefined}
1059
- className="relative"
1060
- draggable={draggable}
1061
- onDragStart={handleDragStart}
1062
- onDragOver={handleDragOver}
1063
- onDragLeave={(event) => {
1064
- // Only clear the indicator when the pointer truly leaves this row.
1065
- // Moving into a child element fires dragleave on the outer div too, so
1066
- // we suppress the clear when relatedTarget is still within this row.
1067
- if (event.currentTarget.contains(event.relatedTarget as Node | null))
1068
- return;
1069
- onDropIndicatorChange(null);
1070
- }}
1071
- onDrop={handleDrop}
1072
- onDragEnd={() => {
1073
- activeDragState = null;
1074
- onDropIndicatorChange(null);
1075
- }}
1076
- onMouseEnter={() => onHoverLayer?.(node.id)}
1077
- onMouseLeave={() => onLeaveLayer?.(node.id)}
1078
- >
1079
- {activeDrop === "before" ? (
1080
- <span className="pointer-events-none absolute left-2 right-2 top-0 z-10 h-px bg-[var(--design-editor-accent-color)]" />
1081
- ) : null}
1082
- {activeDrop === "after" ? (
1083
- <span className="pointer-events-none absolute bottom-0 left-2 right-2 z-10 h-px bg-[var(--design-editor-accent-color)]" />
1084
- ) : null}
1085
- <div
1086
- className={cn(
1087
- "group flex h-8 items-center gap-1 rounded-[5px] pr-1 text-[12px]",
1088
- activeDrop === "inside" &&
1089
- "ring-1 ring-inset ring-[var(--design-editor-accent-color)]",
1090
- isSelected &&
1091
- "bg-[var(--design-editor-selection-color)] text-foreground",
1092
- !isSelected &&
1093
- isInSelectedSubtree &&
1094
- "bg-[var(--design-editor-selected-subtree-color)] text-foreground/95",
1095
- !isSelected &&
1096
- isActiveScreen &&
1097
- "bg-[var(--design-editor-active-row-color)] text-foreground hover:bg-[var(--design-editor-active-row-color)]",
1098
- !isSelected &&
1099
- !isInSelectedSubtree &&
1100
- !isActiveScreen &&
1101
- "text-foreground/90 hover:bg-[var(--design-editor-layer-hover-color)] hover:text-foreground",
1102
- node.hidden && "text-muted-foreground",
1103
- )}
1104
- style={{ paddingLeft: rowIndent(depth) }}
1105
- >
1106
- {hasChildren ? (
1107
- <Button
1108
- type="button"
1109
- variant="ghost"
1110
- size="icon"
1111
- className="size-4 shrink-0 rounded-sm p-0 text-muted-foreground hover:bg-transparent hover:text-foreground"
1112
- aria-label={isExpanded ? labels.collapse : labels.expand}
1113
- onClick={(event) => {
1114
- event.stopPropagation();
1115
- onToggleExpanded(!isExpanded);
1116
- }}
1117
- >
1118
- {isExpanded ? (
1119
- <IconChevronDown className="size-4" />
1120
- ) : (
1121
- <IconChevronRight className="size-4 rtl:-scale-x-100" />
1122
- )}
1123
- </Button>
1124
- ) : (
1125
- <span className="size-4 shrink-0" />
1126
- )}
1137
+ const showContextMenu =
1138
+ selectable &&
1139
+ (Boolean(onRename && node.renamable !== false) || lockable || hideable);
1127
1140
 
1128
- <button
1129
- type="button"
1130
- disabled={!selectable}
1131
- data-layer-row-button
1132
- data-layer-node-id={node.id}
1133
- className={cn(
1134
- "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)]",
1135
- selectable ? "cursor-default" : "cursor-default opacity-80",
1136
- )}
1137
- onClick={handlePointerSelect}
1138
- onDoubleClick={() => onStartRename(node)}
1139
- onKeyDown={handleKeyDown}
1141
+ return (
1142
+ <ContextMenu>
1143
+ <ContextMenuTrigger asChild disabled={!showContextMenu}>
1144
+ <div
1145
+ ref={rowRef}
1146
+ role="treeitem"
1147
+ aria-expanded={hasChildren ? isExpanded : undefined}
1148
+ aria-level={depth + 1}
1149
+ aria-selected={selectable ? isSelected : undefined}
1150
+ className="relative min-w-full"
1151
+ draggable={draggable}
1152
+ onDragStart={handleDragStart}
1153
+ onDragOver={handleDragOver}
1154
+ onDragLeave={(event) => {
1155
+ // Only clear the indicator when the pointer truly leaves this row.
1156
+ // Moving into a child element fires dragleave on the outer div too, so
1157
+ // we suppress the clear when relatedTarget is still within this row.
1158
+ if (
1159
+ event.currentTarget.contains(event.relatedTarget as Node | null)
1160
+ )
1161
+ return;
1162
+ if (activeDropIntent?.targetId === node.id) activeDropIntent = null;
1163
+ onDropIndicatorChange(null);
1164
+ }}
1165
+ onDrop={handleDrop}
1166
+ onDragEnd={() => {
1167
+ activeDragState = null;
1168
+ activeDropIntent = null;
1169
+ onDropIndicatorChange(null);
1170
+ }}
1171
+ onMouseEnter={() => onHoverLayer?.(node.id)}
1172
+ onMouseLeave={() => onLeaveLayer?.(node.id)}
1140
1173
  >
1141
- <span
1174
+ {activeDrop === "before" ? (
1175
+ <span className="pointer-events-none absolute left-2 right-2 top-0 z-10 h-px bg-[var(--design-editor-accent-color)]" />
1176
+ ) : null}
1177
+ {activeDrop === "after" ? (
1178
+ <span className="pointer-events-none absolute bottom-0 left-2 right-2 z-10 h-px bg-[var(--design-editor-accent-color)]" />
1179
+ ) : null}
1180
+ <div
1142
1181
  className={cn(
1143
- "shrink-0 text-muted-foreground",
1144
- (isSelected || isInSelectedSubtree) && "text-foreground",
1182
+ "group flex h-8 min-w-full items-center gap-1 rounded-[5px] pr-1 text-[12px]",
1183
+ activeDrop === "inside" &&
1184
+ "ring-1 ring-inset ring-[var(--design-editor-accent-color)]",
1185
+ isSelected &&
1186
+ "bg-[var(--design-editor-selection-color)] text-foreground",
1187
+ !isSelected &&
1188
+ isInSelectedSubtree &&
1189
+ "bg-[var(--design-editor-selected-subtree-color)] text-foreground/95",
1190
+ !isSelected &&
1191
+ isActiveScreen &&
1192
+ "bg-[var(--design-editor-active-row-color)] text-foreground hover:bg-[var(--design-editor-active-row-color)]",
1193
+ !isSelected &&
1194
+ !isInSelectedSubtree &&
1195
+ !isActiveScreen &&
1196
+ "text-foreground/90 hover:bg-[var(--design-editor-layer-hover-color)] hover:text-foreground",
1197
+ node.hidden && "text-muted-foreground",
1145
1198
  )}
1199
+ style={{ paddingLeft: rowIndent(depth) }}
1146
1200
  >
1147
- {node.icon ?? (
1148
- <LayerGlyph
1149
- node={node}
1150
- selected={isSelected}
1151
- inSelectedSubtree={isInSelectedSubtree}
1152
- />
1153
- )}
1154
- </span>
1155
- {isRenaming ? (
1156
- <input
1157
- autoFocus
1158
- value={renameDraft}
1159
- onClick={(event) => event.stopPropagation()}
1160
- onChange={(event) => onRenameDraftChange(event.target.value)}
1161
- onBlur={() => {
1162
- // Escape sets renameCancelledRef before blur fires; skip commit.
1163
- if (renameCancelledRef.current) {
1164
- renameCancelledRef.current = false;
1165
- return;
1166
- }
1167
- onCommitRename(node.id);
1168
- }}
1169
- onKeyDown={(event) => {
1170
- if (event.key === "Enter") {
1171
- event.preventDefault();
1172
- onCommitRename(node.id);
1173
- } else if (event.key === "Escape") {
1174
- event.preventDefault();
1175
- renameCancelledRef.current = true;
1176
- onCancelRename();
1177
- }
1178
- }}
1179
- 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"
1180
- aria-label={labels.rename}
1181
- />
1182
- ) : (
1183
- <span
1184
- className={cn(
1185
- "min-w-0 flex-1 truncate font-medium leading-none",
1186
- node.hidden && "line-through",
1187
- )}
1188
- >
1189
- {node.name}
1190
- </span>
1191
- )}
1192
- {!isRenaming &&
1193
- layerCanShowBadge(node) &&
1194
- node.badge !== null &&
1195
- node.badge !== undefined ? (
1196
- <span className="shrink-0 rounded-sm bg-muted px-1 text-[10px] text-muted-foreground">
1197
- {node.badge}
1198
- </span>
1199
- ) : null}
1200
- </button>
1201
-
1202
- {lockable ? (
1203
- <Tooltip>
1204
- <TooltipTrigger asChild>
1201
+ {hasChildren ? (
1205
1202
  <Button
1206
1203
  type="button"
1207
1204
  variant="ghost"
1208
1205
  size="icon"
1209
- className={cn(
1210
- "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",
1211
- node.locked && "opacity-100",
1212
- isSelected && "text-foreground",
1213
- )}
1214
- aria-label={node.locked ? labels.unlock : labels.lock}
1206
+ className="size-4 shrink-0 rounded-sm p-0 text-muted-foreground hover:bg-transparent hover:text-foreground"
1207
+ aria-label={isExpanded ? labels.collapse : labels.expand}
1215
1208
  onClick={(event) => {
1216
1209
  event.stopPropagation();
1217
- onToggleLocked?.(node.id, !node.locked);
1210
+ onToggleExpanded(!isExpanded);
1218
1211
  }}
1219
1212
  >
1220
- {node.locked ? (
1221
- <IconLock className="size-3" />
1213
+ {isExpanded ? (
1214
+ <IconChevronDown className="size-4" />
1222
1215
  ) : (
1223
- <IconLockOpen className="size-3" />
1216
+ <IconChevronRight className="size-4 rtl:-scale-x-100" />
1224
1217
  )}
1225
1218
  </Button>
1226
- </TooltipTrigger>
1227
- <TooltipContent>
1228
- {node.locked ? labels.unlock : labels.lock}
1229
- </TooltipContent>
1230
- </Tooltip>
1231
- ) : null}
1219
+ ) : (
1220
+ <span className="size-4 shrink-0" />
1221
+ )}
1232
1222
 
1233
- {hideable ? (
1234
- <Tooltip>
1235
- <TooltipTrigger asChild>
1236
- <Button
1237
- type="button"
1238
- variant="ghost"
1239
- size="icon"
1223
+ <button
1224
+ type="button"
1225
+ disabled={!selectable}
1226
+ data-layer-row-button
1227
+ data-layer-node-id={node.id}
1228
+ className={cn(
1229
+ "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)]",
1230
+ selectable ? "cursor-default" : "cursor-default opacity-80",
1231
+ )}
1232
+ onClick={handlePointerSelect}
1233
+ onDoubleClick={() => onStartRename(node)}
1234
+ onKeyDown={handleKeyDown}
1235
+ >
1236
+ <span
1240
1237
  className={cn(
1241
- "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",
1242
- node.hidden && "opacity-100",
1243
- isSelected && "text-foreground",
1238
+ "shrink-0 text-muted-foreground",
1239
+ (isSelected || isInSelectedSubtree) && "text-foreground",
1244
1240
  )}
1245
- aria-label={node.hidden ? labels.show : labels.hide}
1246
- onClick={(event) => {
1247
- event.stopPropagation();
1248
- onToggleHidden?.(node.id, !node.hidden);
1249
- }}
1250
1241
  >
1251
- {node.hidden ? (
1252
- <IconEyeOff className="size-3" />
1253
- ) : (
1254
- <IconEye className="size-3" />
1242
+ {node.icon ?? (
1243
+ <LayerGlyph
1244
+ node={node}
1245
+ selected={isSelected}
1246
+ inSelectedSubtree={isInSelectedSubtree}
1247
+ />
1255
1248
  )}
1256
- </Button>
1257
- </TooltipTrigger>
1258
- <TooltipContent>
1249
+ </span>
1250
+ {isRenaming ? (
1251
+ <input
1252
+ autoFocus
1253
+ value={renameDraft}
1254
+ onClick={(event) => event.stopPropagation()}
1255
+ onChange={(event) => onRenameDraftChange(event.target.value)}
1256
+ onBlur={() => {
1257
+ // Escape sets renameCancelledRef before blur fires; skip commit.
1258
+ if (renameCancelledRef.current) {
1259
+ renameCancelledRef.current = false;
1260
+ return;
1261
+ }
1262
+ onCommitRename(node.id);
1263
+ }}
1264
+ onKeyDown={(event) => {
1265
+ if (event.key === "Enter") {
1266
+ event.preventDefault();
1267
+ // Stop propagation so the keydown does not bubble up to the
1268
+ // parent row <button>'s handleKeyDown, which would fire
1269
+ // onSelect and potentially trigger canvas-level side effects
1270
+ // (e.g. switching to overview mode or selecting a wrong layer).
1271
+ event.stopPropagation();
1272
+ onCommitRename(node.id);
1273
+ } else if (event.key === "Tab") {
1274
+ // Commit the rename on Tab (Figma behavior) and prevent the
1275
+ // keydown from reaching the global design hotkeys handler which
1276
+ // would cycle the active file when Tab fires outside an input.
1277
+ event.preventDefault();
1278
+ event.stopPropagation();
1279
+ onCommitRename(node.id);
1280
+ } else if (event.key === "Escape") {
1281
+ event.preventDefault();
1282
+ event.stopPropagation();
1283
+ renameCancelledRef.current = true;
1284
+ onCancelRename();
1285
+ }
1286
+ }}
1287
+ 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"
1288
+ aria-label={labels.rename}
1289
+ />
1290
+ ) : (
1291
+ <span
1292
+ className={cn(
1293
+ "min-w-0 flex-1 truncate font-medium leading-none",
1294
+ node.hidden && "line-through",
1295
+ )}
1296
+ title={node.name}
1297
+ >
1298
+ {node.name}
1299
+ </span>
1300
+ )}
1301
+ {!isRenaming &&
1302
+ layerCanShowBadge(node) &&
1303
+ node.badge !== null &&
1304
+ node.badge !== undefined ? (
1305
+ <span className="shrink-0 rounded-sm bg-muted px-1 text-[10px] text-muted-foreground">
1306
+ {node.badge}
1307
+ </span>
1308
+ ) : null}
1309
+ </button>
1310
+
1311
+ {lockable ? (
1312
+ <Tooltip>
1313
+ <TooltipTrigger asChild>
1314
+ <Button
1315
+ type="button"
1316
+ variant="ghost"
1317
+ size="icon"
1318
+ className={cn(
1319
+ "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",
1320
+ node.locked && "opacity-100",
1321
+ isSelected && "text-foreground",
1322
+ )}
1323
+ aria-label={node.locked ? labels.unlock : labels.lock}
1324
+ onClick={(event) => {
1325
+ event.stopPropagation();
1326
+ onToggleLocked?.(node.id, !node.locked);
1327
+ }}
1328
+ >
1329
+ {node.locked ? (
1330
+ <IconLock className="size-3" />
1331
+ ) : (
1332
+ <IconLockOpen className="size-3" />
1333
+ )}
1334
+ </Button>
1335
+ </TooltipTrigger>
1336
+ <TooltipContent>
1337
+ {node.locked ? labels.unlock : labels.lock}
1338
+ </TooltipContent>
1339
+ </Tooltip>
1340
+ ) : null}
1341
+
1342
+ {hideable ? (
1343
+ <Tooltip>
1344
+ <TooltipTrigger asChild>
1345
+ <Button
1346
+ type="button"
1347
+ variant="ghost"
1348
+ size="icon"
1349
+ className={cn(
1350
+ "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",
1351
+ node.hidden && "opacity-100",
1352
+ isSelected && "text-foreground",
1353
+ )}
1354
+ aria-label={node.hidden ? labels.show : labels.hide}
1355
+ onClick={(event) => {
1356
+ event.stopPropagation();
1357
+ onToggleHidden?.(node.id, !node.hidden);
1358
+ }}
1359
+ >
1360
+ {node.hidden ? (
1361
+ <IconEyeOff className="size-3" />
1362
+ ) : (
1363
+ <IconEye className="size-3" />
1364
+ )}
1365
+ </Button>
1366
+ </TooltipTrigger>
1367
+ <TooltipContent>
1368
+ {node.hidden ? labels.show : labels.hide}
1369
+ </TooltipContent>
1370
+ </Tooltip>
1371
+ ) : null}
1372
+ </div>
1373
+ </div>
1374
+ </ContextMenuTrigger>
1375
+ {showContextMenu ? (
1376
+ <ContextMenuContent className="z-[300] min-w-[160px] text-[12px]">
1377
+ {onRename && node.renamable !== false ? (
1378
+ <ContextMenuItem
1379
+ className="gap-2 text-[12px]"
1380
+ onSelect={() => onStartRename(node)}
1381
+ >
1382
+ <IconPencil className="size-3.5 text-muted-foreground" />
1383
+ {labels.rename}
1384
+ </ContextMenuItem>
1385
+ ) : null}
1386
+ {onRename && node.renamable !== false && (lockable || hideable) ? (
1387
+ <ContextMenuSeparator />
1388
+ ) : null}
1389
+ {lockable ? (
1390
+ <ContextMenuItem
1391
+ className="gap-2 text-[12px]"
1392
+ onSelect={() => onToggleLocked?.(node.id, !node.locked)}
1393
+ >
1394
+ {node.locked ? (
1395
+ <IconLockOpen className="size-3.5 text-muted-foreground" />
1396
+ ) : (
1397
+ <IconLock className="size-3.5 text-muted-foreground" />
1398
+ )}
1399
+ {node.locked ? labels.unlock : labels.lock}
1400
+ </ContextMenuItem>
1401
+ ) : null}
1402
+ {hideable ? (
1403
+ <ContextMenuItem
1404
+ className="gap-2 text-[12px]"
1405
+ onSelect={() => onToggleHidden?.(node.id, !node.hidden)}
1406
+ >
1407
+ {node.hidden ? (
1408
+ <IconEye className="size-3.5 text-muted-foreground" />
1409
+ ) : (
1410
+ <IconEyeOff className="size-3.5 text-muted-foreground" />
1411
+ )}
1259
1412
  {node.hidden ? labels.show : labels.hide}
1260
- </TooltipContent>
1261
- </Tooltip>
1262
- ) : null}
1263
- </div>
1264
- </div>
1413
+ </ContextMenuItem>
1414
+ ) : null}
1415
+ </ContextMenuContent>
1416
+ ) : null}
1417
+ </ContextMenu>
1265
1418
  );
1266
1419
  }
1267
1420
 
@@ -1623,6 +1776,18 @@ function ElementLayerGlyph({ className }: { className?: string }) {
1623
1776
  );
1624
1777
  }
1625
1778
 
1779
+ function layerCanDropInside(node: LayersPanelNode, hasChildren: boolean) {
1780
+ return (
1781
+ hasChildren ||
1782
+ Boolean(node.layout?.isFlexContainer || node.layout?.isGridContainer) ||
1783
+ node.type === "file" ||
1784
+ node.type === "screen" ||
1785
+ node.type === "frame" ||
1786
+ node.type === "group" ||
1787
+ node.type === "section"
1788
+ );
1789
+ }
1790
+
1626
1791
  function dropPlacementForEvent(
1627
1792
  event: DragEvent<HTMLDivElement>,
1628
1793
  canDropInside: boolean,