@agent-native/core 0.80.10 → 0.81.0

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 (267) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +53 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +108 -15
  5. package/corpus/core/src/cli/design-connect.ts +419 -19
  6. package/corpus/core/src/client/AssistantChat.tsx +123 -26
  7. package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
  8. package/corpus/core/src/client/chat/index.ts +1 -0
  9. package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
  10. package/corpus/core/src/client/chat-view-transition.ts +33 -2
  11. package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
  12. package/corpus/core/src/client/index.ts +1 -0
  13. package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
  14. package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
  15. package/corpus/core/src/client/sse-event-processor.ts +9 -0
  16. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
  17. package/corpus/core/src/db/client.ts +29 -2
  18. package/corpus/core/src/provider-api/index.ts +29 -0
  19. package/corpus/core/src/server/core-routes-plugin.ts +58 -20
  20. package/corpus/core/src/styles/agent-native.css +54 -0
  21. package/corpus/core/src/usage/store.ts +2 -2
  22. package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
  23. package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
  24. package/corpus/templates/analytics/app/global.css +8 -0
  25. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
  26. package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
  27. package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
  28. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
  29. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
  30. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
  31. package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
  33. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
  34. package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
  35. package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
  36. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
  37. package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
  38. package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +9 -2
  39. package/corpus/templates/assets/AGENTS.md +4 -0
  40. package/corpus/templates/assets/actions/_tool-activity.ts +46 -0
  41. package/corpus/templates/assets/actions/generate-image-batch.ts +26 -5
  42. package/corpus/templates/assets/actions/generate-image.ts +118 -67
  43. package/corpus/templates/assets/actions/list-draft-assets.ts +50 -0
  44. package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
  45. package/corpus/templates/assets/actions/rerun-generation-run.ts +52 -31
  46. package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +100 -0
  47. package/corpus/templates/assets/app/global.css +15 -0
  48. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
  49. package/corpus/templates/assets/app/i18n/zh-TW.ts +7 -0
  50. package/corpus/templates/assets/app/i18n-data.ts +61 -0
  51. package/corpus/templates/assets/app/routes/_index.tsx +4 -0
  52. package/corpus/templates/assets/app/routes/library.tsx +233 -52
  53. package/corpus/templates/assets/changelog/2026-06-29-image-generation-can-now-render-requested-text-and-respect-b.md +6 -0
  54. package/corpus/templates/assets/changelog/2026-06-30-image-generation-no-longer-looks-stuck-while-the-provider-is-still-working.md +6 -0
  55. package/corpus/templates/assets/changelog/2026-06-30-tagged-presets-now-control-the-image-aspect-ratio.md +6 -0
  56. package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
  57. package/corpus/templates/assets/server/lib/generation.ts +135 -11
  58. package/corpus/templates/assets/shared/api.ts +4 -0
  59. package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
  60. package/corpus/templates/clips/actions/list-ai-requests.ts +60 -0
  61. package/corpus/templates/clips/app/hooks/use-auto-title.ts +24 -25
  62. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
  63. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
  64. package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
  65. package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
  66. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  67. package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
  68. package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
  69. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
  70. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
  71. package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
  72. package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
  73. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
  74. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
  75. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
  76. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
  77. package/corpus/templates/design/AGENTS.md +27 -0
  78. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
  79. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
  80. package/corpus/templates/design/actions/apply-component-prop-edit.ts +76 -25
  81. package/corpus/templates/design/actions/apply-design-state.ts +17 -4
  82. package/corpus/templates/design/actions/apply-motion-edit.ts +76 -115
  83. package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
  84. package/corpus/templates/design/actions/capture-design-state.ts +27 -4
  85. package/corpus/templates/design/actions/connect-localhost.ts +59 -22
  86. package/corpus/templates/design/actions/delete-design.ts +20 -0
  87. package/corpus/templates/design/actions/export-zip.ts +23 -8
  88. package/corpus/templates/design/actions/generate-design.ts +53 -1
  89. package/corpus/templates/design/actions/get-component-details.ts +21 -26
  90. package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
  91. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
  92. package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
  93. package/corpus/templates/design/actions/index-design-tokens.ts +18 -5
  94. package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
  95. package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
  96. package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
  97. package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
  98. package/corpus/templates/design/actions/list-design-states.ts +19 -1
  99. package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
  100. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
  101. package/corpus/templates/design/actions/navigate.ts +18 -3
  102. package/corpus/templates/design/actions/open-component-source.ts +7 -2
  103. package/corpus/templates/design/actions/preview-component-prop-edit.ts +32 -26
  104. package/corpus/templates/design/actions/preview-shader-fill.ts +9 -11
  105. package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
  106. package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
  107. package/corpus/templates/design/actions/provider-api-request.ts +3 -3
  108. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
  109. package/corpus/templates/design/actions/run-design-audit.ts +74 -10
  110. package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
  111. package/corpus/templates/design/actions/view-screen.ts +1 -1
  112. package/corpus/templates/design/actions/write-local-file.ts +213 -0
  113. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +244 -3197
  114. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
  115. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +795 -289
  116. package/corpus/templates/design/app/components/design/EditPanel.tsx +635 -234
  117. package/corpus/templates/design/app/components/design/LayersPanel.tsx +124 -32
  118. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
  119. package/corpus/templates/design/app/components/design/MotionDock.tsx +244 -266
  120. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +491 -56
  121. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +55 -67
  122. package/corpus/templates/design/app/components/design/StatesPanel.tsx +38 -63
  123. package/corpus/templates/design/app/components/design/TokensPanel.tsx +264 -8
  124. package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
  125. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
  126. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
  127. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
  128. package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
  129. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
  130. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
  131. package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
  132. package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
  133. package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
  134. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
  135. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
  136. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
  137. package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
  138. package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
  139. package/corpus/templates/design/app/global.css +7 -0
  140. package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
  141. package/corpus/templates/design/app/i18n/zh-TW.ts +22 -0
  142. package/corpus/templates/design/app/i18n-data.ts +369 -0
  143. package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
  144. package/corpus/templates/design/app/pages/DesignEditor.tsx +3024 -616
  145. package/corpus/templates/design/app/pages/Index.tsx +5 -1
  146. package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
  147. package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
  148. package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
  149. package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
  150. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
  151. package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
  152. package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
  153. package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
  154. package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
  155. package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
  156. package/corpus/templates/design/changelog/2026-06-30-design-token-edits-now-stay-visible-in-previews-and-token-li.md +6 -0
  157. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
  158. package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
  159. package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
  160. package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
  161. package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
  162. package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
  163. package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
  164. package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
  165. package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
  166. package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
  167. package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
  168. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
  169. package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
  170. package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
  171. package/corpus/templates/design/e2e/global-setup.ts +94 -2
  172. package/corpus/templates/design/e2e/helpers.ts +168 -65
  173. package/corpus/templates/design/package.json +3 -1
  174. package/corpus/templates/design/server/db/schema.ts +23 -0
  175. package/corpus/templates/design/server/handlers/uploads.ts +22 -1
  176. package/corpus/templates/design/server/lib/provider-api.ts +7 -4
  177. package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
  178. package/corpus/templates/design/server/plugins/db.ts +39 -0
  179. package/corpus/templates/design/server/register-secrets.ts +38 -0
  180. package/corpus/templates/design/shared/board-file.ts +228 -0
  181. package/corpus/templates/design/shared/board-objects.ts +288 -0
  182. package/corpus/templates/design/shared/code-layer.ts +52 -0
  183. package/corpus/templates/design/shared/component-model.ts +35 -0
  184. package/corpus/templates/design/shared/motion-compiler.ts +137 -15
  185. package/corpus/templates/design/shared/motion-timeline.ts +2 -2
  186. package/corpus/templates/design/shared/resolve-tweaks.ts +32 -9
  187. package/corpus/templates/design/tsconfig.json +2 -1
  188. package/corpus/templates/slides/AGENTS.md +9 -0
  189. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
  190. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
  191. package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
  192. package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
  193. package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
  194. package/corpus/templates/slides/app/vite-env.d.ts +18 -0
  195. package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
  196. package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
  197. package/corpus/templates/slides/package.json +1 -0
  198. package/corpus/templates/slides/vite.config.ts +1 -0
  199. package/dist/agent/production-agent.d.ts.map +1 -1
  200. package/dist/agent/production-agent.js +94 -10
  201. package/dist/agent/production-agent.js.map +1 -1
  202. package/dist/cli/design-connect.d.ts +25 -0
  203. package/dist/cli/design-connect.d.ts.map +1 -1
  204. package/dist/cli/design-connect.js +357 -18
  205. package/dist/cli/design-connect.js.map +1 -1
  206. package/dist/client/AssistantChat.d.ts +14 -0
  207. package/dist/client/AssistantChat.d.ts.map +1 -1
  208. package/dist/client/AssistantChat.js +115 -22
  209. package/dist/client/AssistantChat.js.map +1 -1
  210. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  211. package/dist/client/ConnectBuilderCard.js +1 -0
  212. package/dist/client/ConnectBuilderCard.js.map +1 -1
  213. package/dist/client/chat/index.d.ts +1 -1
  214. package/dist/client/chat/index.d.ts.map +1 -1
  215. package/dist/client/chat/index.js +1 -1
  216. package/dist/client/chat/index.js.map +1 -1
  217. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  218. package/dist/client/chat/run-recovery.js +5 -5
  219. package/dist/client/chat/run-recovery.js.map +1 -1
  220. package/dist/client/chat-view-transition.d.ts +6 -0
  221. package/dist/client/chat-view-transition.d.ts.map +1 -1
  222. package/dist/client/chat-view-transition.js +23 -2
  223. package/dist/client/chat-view-transition.js.map +1 -1
  224. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  225. package/dist/client/composer/PromptComposer.js +3 -2
  226. package/dist/client/composer/PromptComposer.js.map +1 -1
  227. package/dist/client/index.d.ts +1 -1
  228. package/dist/client/index.d.ts.map +1 -1
  229. package/dist/client/index.js +1 -1
  230. package/dist/client/index.js.map +1 -1
  231. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  232. package/dist/client/org/OrgSwitcher.js +11 -4
  233. package/dist/client/org/OrgSwitcher.js.map +1 -1
  234. package/dist/client/sharing/ShareButton.js +3 -3
  235. package/dist/client/sharing/ShareButton.js.map +1 -1
  236. package/dist/client/sse-event-processor.d.ts.map +1 -1
  237. package/dist/client/sse-event-processor.js +10 -0
  238. package/dist/client/sse-event-processor.js.map +1 -1
  239. package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
  240. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  241. package/dist/client/use-agent-chat-home-handoff.js +20 -6
  242. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  243. package/dist/collab/awareness.d.ts +2 -2
  244. package/dist/collab/awareness.d.ts.map +1 -1
  245. package/dist/collab/routes.d.ts +2 -2
  246. package/dist/db/client.d.ts +1 -0
  247. package/dist/db/client.d.ts.map +1 -1
  248. package/dist/db/client.js +28 -2
  249. package/dist/db/client.js.map +1 -1
  250. package/dist/notifications/routes.d.ts +3 -3
  251. package/dist/observability/routes.d.ts +3 -3
  252. package/dist/progress/routes.d.ts +1 -1
  253. package/dist/provider-api/index.d.ts +5 -4
  254. package/dist/provider-api/index.d.ts.map +1 -1
  255. package/dist/provider-api/index.js +28 -0
  256. package/dist/provider-api/index.js.map +1 -1
  257. package/dist/resources/handlers.d.ts +3 -3
  258. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  259. package/dist/server/core-routes-plugin.d.ts +25 -0
  260. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  261. package/dist/server/core-routes-plugin.js +43 -18
  262. package/dist/server/core-routes-plugin.js.map +1 -1
  263. package/dist/server/transcribe-voice.d.ts +1 -1
  264. package/dist/styles/agent-native.css +54 -0
  265. package/dist/usage/store.js +2 -2
  266. package/dist/usage/store.js.map +1 -1
  267. package/package.json +1 -1
@@ -57,6 +57,7 @@ export type LayersPanelNodeType =
57
57
  | "image"
58
58
  | "code"
59
59
  | "element"
60
+ | "board-element"
60
61
  | "unknown";
61
62
 
62
63
  export interface LayersPanelNode {
@@ -151,6 +152,7 @@ export interface LayersPanelProps {
151
152
  expandedIds: readonly string[];
152
153
  searchQuery: string;
153
154
  className?: string;
155
+ footer?: ReactNode;
154
156
  labels?: Partial<LayersPanelLabels>;
155
157
  onSearchQueryChange: (query: string) => void;
156
158
  onScreenSelect?: (id: string) => void;
@@ -168,9 +170,12 @@ export interface LayersPanelProps {
168
170
  onLeaveLayer?: (id: string) => void;
169
171
  onMoveLayer?: (intent: LayersPanelMoveIntent) => void;
170
172
  canMoveLayer?: (intent: LayersPanelMoveIntent) => boolean;
173
+ // Board elements — top-level layer nodes projected from the board file.
174
+ // When absent the panel is unchanged.
175
+ boardElements?: LayersPanelNode[];
171
176
  }
172
177
 
173
- interface FlatLayerRow {
178
+ export interface FlatLayerRow {
174
179
  node: LayersPanelNode;
175
180
  rowKey: string;
176
181
  depth: number;
@@ -283,14 +288,16 @@ function buildRootNodes({
283
288
  layers,
284
289
  codeLayers,
285
290
  elementLayers,
291
+ boardElements,
286
292
  labels,
287
293
  }: Pick<
288
294
  LayersPanelProps,
289
- "files" | "layers" | "codeLayers" | "elementLayers"
295
+ "files" | "layers" | "codeLayers" | "elementLayers" | "boardElements"
290
296
  > & {
291
297
  labels: LayersPanelLabels;
292
298
  }) {
293
299
  const roots: LayersPanelNode[] = [
300
+ ...(boardElements ?? []),
294
301
  ...(files?.map(asFileNode) ?? []),
295
302
  ...(layers ?? []),
296
303
  ];
@@ -412,6 +419,65 @@ function collectAncestorIds(
412
419
  return Array.from(ancestors);
413
420
  }
414
421
 
422
+ export function getLayerSelectionAnchorFromExternalSelection(args: {
423
+ selectedIds: readonly string[];
424
+ selectableVisibleIds: readonly string[];
425
+ }): string | null {
426
+ const selectableVisibleIdSet = new Set(args.selectableVisibleIds);
427
+ return (
428
+ [...args.selectedIds]
429
+ .reverse()
430
+ .find((id) => selectableVisibleIdSet.has(id)) ?? null
431
+ );
432
+ }
433
+
434
+ export function getTreeOrderedLayerIds(
435
+ ids: readonly string[],
436
+ visibleRows: readonly FlatLayerRow[],
437
+ ): string[] {
438
+ const idSet = new Set(ids.filter((id) => id && !id.startsWith("__")));
439
+ const orderedIds = visibleRows
440
+ .map((row) => row.node.id)
441
+ .filter((id) => idSet.has(id));
442
+ const orderedIdSet = new Set(orderedIds);
443
+ return [
444
+ ...orderedIds,
445
+ ...ids.filter((id) => id && !id.startsWith("__") && !orderedIdSet.has(id)),
446
+ ];
447
+ }
448
+
449
+ export function getDraggedLayerIdsForRows(args: {
450
+ selectedIds: readonly string[];
451
+ nodeId: string;
452
+ visibleRows: readonly FlatLayerRow[];
453
+ }): string[] {
454
+ const rawDraggedIds = args.selectedIds.includes(args.nodeId)
455
+ ? getTreeOrderedLayerIds(args.selectedIds, args.visibleRows)
456
+ : [args.nodeId];
457
+ const draggedIdSet = new Set(rawDraggedIds);
458
+ return rawDraggedIds.filter((id) => {
459
+ const rowForId = args.visibleRows.find((row) => row.node.id === id);
460
+ return !rowForId?.ancestorIds.some((ancestorId) =>
461
+ draggedIdSet.has(ancestorId),
462
+ );
463
+ });
464
+ }
465
+
466
+ export function shouldResyncLayerSelectionAnchor(args: {
467
+ selectionSignature: string;
468
+ lastPanelSelectionSignature: string;
469
+ currentAnchor: string | null;
470
+ selectableVisibleIds: readonly string[];
471
+ }) {
472
+ const anchorStillVisible =
473
+ args.currentAnchor !== null &&
474
+ args.selectableVisibleIds.includes(args.currentAnchor);
475
+ return (
476
+ args.selectionSignature !== args.lastPanelSelectionSignature ||
477
+ !anchorStillVisible
478
+ );
479
+ }
480
+
415
481
  function layerCanShowBadge(node: LayersPanelNode) {
416
482
  return (
417
483
  node.type === "file" ||
@@ -432,6 +498,7 @@ export function LayersPanel({
432
498
  expandedIds,
433
499
  searchQuery,
434
500
  className,
501
+ footer,
435
502
  labels: labelsProp,
436
503
  onSearchQueryChange,
437
504
  onScreenSelect,
@@ -446,11 +513,13 @@ export function LayersPanel({
446
513
  onLeaveLayer,
447
514
  onMoveLayer,
448
515
  canMoveLayer,
516
+ boardElements,
449
517
  }: LayersPanelProps) {
450
518
  const t = useT();
451
519
  const labels = useMemo(() => mergeLabels(labelsProp, t), [labelsProp, t]);
452
520
  const selectedIdSet = useMemo(() => new Set(selectedIds), [selectedIds]);
453
521
  const selectedIdsRef = useRef<readonly string[]>(selectedIds);
522
+ const lastPanelSelectionSignatureRef = useRef(selectedIds.join("\0"));
454
523
  const expandedIdSet = useMemo(() => new Set(expandedIds), [expandedIds]);
455
524
  const lastSelectionAnchorRef = useRef<string | null>(selectedIds[0] ?? null);
456
525
  const searchInputRef = useRef<HTMLInputElement>(null);
@@ -469,9 +538,10 @@ export function LayersPanel({
469
538
  layers,
470
539
  codeLayers,
471
540
  elementLayers,
541
+ boardElements,
472
542
  labels,
473
543
  }),
474
- [codeLayers, elementLayers, files, labels, layers],
544
+ [boardElements, codeLayers, elementLayers, files, labels, layers],
475
545
  );
476
546
 
477
547
  const visibleRows = useMemo(() => {
@@ -486,9 +556,35 @@ export function LayersPanel({
486
556
  [roots, selectedIdSet],
487
557
  );
488
558
 
559
+ const selectableVisibleIds = useMemo(
560
+ () =>
561
+ visibleRows
562
+ .map(({ node }) => node)
563
+ .filter((node) => node.selectable !== false)
564
+ .map((node) => node.id),
565
+ [visibleRows],
566
+ );
567
+
489
568
  useLayoutEffect(() => {
490
569
  selectedIdsRef.current = selectedIds;
491
- }, [selectedIds]);
570
+ const signature = selectedIds.join("\0");
571
+ if (
572
+ !shouldResyncLayerSelectionAnchor({
573
+ selectionSignature: signature,
574
+ lastPanelSelectionSignature: lastPanelSelectionSignatureRef.current,
575
+ currentAnchor: lastSelectionAnchorRef.current,
576
+ selectableVisibleIds,
577
+ })
578
+ ) {
579
+ return;
580
+ }
581
+ lastPanelSelectionSignatureRef.current = signature;
582
+ lastSelectionAnchorRef.current =
583
+ getLayerSelectionAnchorFromExternalSelection({
584
+ selectedIds,
585
+ selectableVisibleIds,
586
+ });
587
+ }, [selectableVisibleIds, selectedIds]);
492
588
 
493
589
  useEffect(() => {
494
590
  if (selectedAncestorIds.length === 0) return;
@@ -522,15 +618,6 @@ export function LayersPanel({
522
618
  return () => window.cancelAnimationFrame(frame);
523
619
  }, [selectedScrollRowKey]);
524
620
 
525
- const selectableVisibleIds = useMemo(
526
- () =>
527
- visibleRows
528
- .map(({ node }) => node)
529
- .filter((node) => node.selectable !== false)
530
- .map((node) => node.id),
531
- [visibleRows],
532
- );
533
-
534
621
  const selectNode = useCallback(
535
622
  (
536
623
  id: string,
@@ -582,6 +669,7 @@ export function LayersPanel({
582
669
  if (!options.range) {
583
670
  lastSelectionAnchorRef.current = id;
584
671
  }
672
+ lastPanelSelectionSignatureRef.current = nextIds.join("\0");
585
673
  onSelectionChange(nextIds, { id, selectedIds: nextIds, ...options });
586
674
  },
587
675
  [onSelectionChange, selectableVisibleIds],
@@ -838,6 +926,7 @@ export function LayersPanel({
838
926
  </div>
839
927
  )}
840
928
  </div>
929
+ {footer ? <div className="shrink-0">{footer}</div> : null}
841
930
  </aside>
842
931
  );
843
932
  }
@@ -948,12 +1037,15 @@ function LayerRow({
948
1037
  };
949
1038
 
950
1039
  const handleKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {
951
- const focusVisibleButton = (nextIndex: number) => {
1040
+ const getFocusableButtons = () => {
952
1041
  const tree = event.currentTarget.closest('[role="tree"]');
953
- if (!tree) return false;
954
- const buttons = Array.from(
1042
+ if (!tree) return [];
1043
+ return Array.from(
955
1044
  tree.querySelectorAll<HTMLButtonElement>("[data-layer-row-button]"),
956
1045
  ).filter((button) => !button.disabled);
1046
+ };
1047
+ const focusVisibleButton = (nextIndex: number) => {
1048
+ const buttons = getFocusableButtons();
957
1049
  const target =
958
1050
  buttons[Math.max(0, Math.min(buttons.length - 1, nextIndex))];
959
1051
  target?.focus();
@@ -971,6 +1063,8 @@ function LayerRow({
971
1063
  const currentIndex = visibleRows.findIndex(
972
1064
  (visibleRow) => visibleRow.rowKey === row.rowKey,
973
1065
  );
1066
+ const focusableButtons = getFocusableButtons();
1067
+ const currentFocusableIndex = focusableButtons.indexOf(event.currentTarget);
974
1068
 
975
1069
  if (event.key === "Enter" || event.key === " " || event.key === "Space") {
976
1070
  event.preventDefault();
@@ -990,12 +1084,12 @@ function LayerRow({
990
1084
  }
991
1085
  if (event.key === "ArrowDown") {
992
1086
  event.preventDefault();
993
- focusVisibleButton(currentIndex + 1);
1087
+ focusVisibleButton(currentFocusableIndex + 1);
994
1088
  return;
995
1089
  }
996
1090
  if (event.key === "ArrowUp") {
997
1091
  event.preventDefault();
998
- focusVisibleButton(currentIndex - 1);
1092
+ focusVisibleButton(currentFocusableIndex - 1);
999
1093
  return;
1000
1094
  }
1001
1095
  if (event.key === "Home") {
@@ -1005,7 +1099,7 @@ function LayerRow({
1005
1099
  }
1006
1100
  if (event.key === "End") {
1007
1101
  event.preventDefault();
1008
- focusVisibleButton(visibleRows.length - 1);
1102
+ focusVisibleButton(focusableButtons.length - 1);
1009
1103
  return;
1010
1104
  }
1011
1105
  if (event.key === "ArrowRight" && hasChildren && !isExpanded) {
@@ -1015,9 +1109,13 @@ function LayerRow({
1015
1109
  }
1016
1110
  if (event.key === "ArrowRight" && hasChildren && isExpanded) {
1017
1111
  event.preventDefault();
1018
- const child = visibleRows[currentIndex + 1];
1112
+ const nextButton = focusableButtons[currentFocusableIndex + 1];
1113
+ const childId = nextButton?.dataset.layerNodeId;
1114
+ const child = childId
1115
+ ? visibleRows.find((visibleRow) => visibleRow.node.id === childId)
1116
+ : null;
1019
1117
  if (child?.ancestorIds.includes(node.id)) {
1020
- focusVisibleButton(currentIndex + 1);
1118
+ focusVisibleButton(currentFocusableIndex + 1);
1021
1119
  }
1022
1120
  return;
1023
1121
  }
@@ -1038,17 +1136,10 @@ function LayerRow({
1038
1136
  event.preventDefault();
1039
1137
  return;
1040
1138
  }
1041
- const rawDraggedIds = selectedIds.includes(node.id)
1042
- ? selectedIds.filter((id) => !id.startsWith("__"))
1043
- : [node.id];
1044
- // Remove any node whose ancestor is also being dragged to avoid double-moves.
1045
- // When a parent is moved, its children move with it automatically.
1046
- const draggedIdSet = new Set(rawDraggedIds);
1047
- const draggedIds = rawDraggedIds.filter((id) => {
1048
- const rowForId = visibleRows.find((r) => r.node.id === id);
1049
- return !rowForId?.ancestorIds.some((ancestorId) =>
1050
- draggedIdSet.has(ancestorId),
1051
- );
1139
+ const draggedIds = getDraggedLayerIdsForRows({
1140
+ selectedIds,
1141
+ nodeId: node.id,
1142
+ visibleRows,
1052
1143
  });
1053
1144
  event.dataTransfer.effectAllowed = "move";
1054
1145
  event.dataTransfer.setData("application/x-design-layer-id", node.id);
@@ -1477,6 +1568,7 @@ function LayerGlyph({
1477
1568
  case "component":
1478
1569
  case "instance":
1479
1570
  return <ComponentLayerGlyph className={cn(common, componentColor)} />;
1571
+ case "board-element":
1480
1572
  case "shape":
1481
1573
  case "rectangle":
1482
1574
  return <RectangleLayerGlyph className={common} />;
@@ -0,0 +1,161 @@
1
+ import { callAction } from "@agent-native/core/client";
2
+ import { IconDeviceFloppy, IconFolderOpen } from "@tabler/icons-react";
3
+ import { useState } from "react";
4
+
5
+ import { Button } from "@/components/ui/button";
6
+ import {
7
+ Dialog,
8
+ DialogContent,
9
+ DialogDescription,
10
+ DialogFooter,
11
+ DialogHeader,
12
+ DialogTitle,
13
+ } from "@/components/ui/dialog";
14
+ import { Spinner } from "@/components/ui/spinner";
15
+
16
+ export interface LocalhostWriteConsentPayload {
17
+ /** The path being granted write access. */
18
+ rootPath: string;
19
+ /** File(s) about to be written (for display only). */
20
+ files: string[];
21
+ /** Pending callback to invoke after user grants consent. */
22
+ onGranted: (grant: {
23
+ grantId: string;
24
+ bridgeToken: string;
25
+ rootPath: string;
26
+ grantedUntil: string;
27
+ }) => void;
28
+ /** Called when the user cancels. */
29
+ onCancel: () => void;
30
+ }
31
+
32
+ interface LocalhostWriteConsentDialogProps {
33
+ open: boolean;
34
+ onOpenChange: (open: boolean) => void;
35
+ designId: string;
36
+ connectionId: string;
37
+ payload: LocalhostWriteConsentPayload | null;
38
+ }
39
+
40
+ /**
41
+ * Modal dialog requesting explicit user consent before the agent writes local
42
+ * files. Shows the rootPath that will be granted write access and the specific
43
+ * file(s) about to be modified. The grant expires after 8 hours and is scoped
44
+ * to that folder only.
45
+ */
46
+ export function LocalhostWriteConsentDialog({
47
+ open,
48
+ onOpenChange,
49
+ designId,
50
+ connectionId,
51
+ payload,
52
+ }: LocalhostWriteConsentDialogProps) {
53
+ const [granting, setGranting] = useState(false);
54
+
55
+ async function handleAllowWrites() {
56
+ if (!payload || !designId || !connectionId) return;
57
+ setGranting(true);
58
+ try {
59
+ const result = await callAction<{
60
+ grantId: string;
61
+ bridgeToken: string;
62
+ rootPath: string;
63
+ grantedUntil: string;
64
+ }>("grant-localhost-write-consent", {
65
+ designId,
66
+ connectionId,
67
+ });
68
+ onOpenChange(false);
69
+ payload.onGranted(result);
70
+ } catch {
71
+ // Silently close; the caller's onCancel path already guards the write.
72
+ onOpenChange(false);
73
+ payload.onCancel();
74
+ } finally {
75
+ setGranting(false);
76
+ }
77
+ }
78
+
79
+ function handleCancel() {
80
+ payload?.onCancel();
81
+ onOpenChange(false);
82
+ }
83
+
84
+ if (!payload) return null;
85
+
86
+ return (
87
+ <Dialog
88
+ open={open}
89
+ onOpenChange={(next) => {
90
+ if (!next) handleCancel();
91
+ else onOpenChange(next);
92
+ }}
93
+ >
94
+ <DialogContent className="max-w-md">
95
+ <DialogHeader>
96
+ <DialogTitle className="flex items-center gap-2">
97
+ <IconDeviceFloppy className="size-4" />
98
+ {"Allow file writes" /* i18n-ignore */}
99
+ </DialogTitle>
100
+ <DialogDescription>
101
+ {
102
+ "The agent wants to write source files on your machine. This access is scoped to the folder below and expires automatically after 8 hours." /* i18n-ignore */
103
+ }
104
+ </DialogDescription>
105
+ </DialogHeader>
106
+
107
+ <div className="space-y-3 py-1 text-sm">
108
+ <div className="rounded-lg border bg-muted/50 px-3 py-2">
109
+ <div className="flex items-center gap-2 text-xs font-medium text-muted-foreground mb-1">
110
+ <IconFolderOpen className="size-3.5 shrink-0" />
111
+ {"Root folder" /* i18n-ignore */}
112
+ </div>
113
+ <code className="break-all text-xs text-foreground">
114
+ {payload.rootPath}
115
+ </code>
116
+ </div>
117
+
118
+ {payload.files.length > 0 && (
119
+ <div className="space-y-1">
120
+ <p className="text-xs font-medium text-muted-foreground">
121
+ {"Files to be written:" /* i18n-ignore */}
122
+ </p>
123
+ <ul className="space-y-0.5 pl-1">
124
+ {payload.files.map((file) => (
125
+ <li key={file} className="flex items-center gap-1.5 text-xs">
126
+ <span className="size-1 rounded-full bg-muted-foreground/50 shrink-0" />
127
+ <code className="break-all">{file}</code>
128
+ </li>
129
+ ))}
130
+ </ul>
131
+ </div>
132
+ )}
133
+
134
+ <p className="text-xs text-muted-foreground">
135
+ {
136
+ "Only .html and .css files can be written. Paths outside the root folder are always blocked." /* i18n-ignore */
137
+ }
138
+ </p>
139
+ </div>
140
+
141
+ <DialogFooter className="gap-2">
142
+ <Button variant="outline" onClick={handleCancel} disabled={granting}>
143
+ {"Cancel" /* i18n-ignore */}
144
+ </Button>
145
+ <Button onClick={() => void handleAllowWrites()} disabled={granting}>
146
+ {
147
+ granting ? (
148
+ <>
149
+ <Spinner className="mr-2 size-3.5" />
150
+ {"Granting…" /* i18n-ignore */}
151
+ </>
152
+ ) : (
153
+ "Allow writes"
154
+ ) /* i18n-ignore */
155
+ }
156
+ </Button>
157
+ </DialogFooter>
158
+ </DialogContent>
159
+ </Dialog>
160
+ );
161
+ }