@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
@@ -6,6 +6,18 @@ import { z } from "zod";
6
6
  import { getDb, schema } from "../server/db/index.js";
7
7
  import "../server/db/index.js"; // ensure registerShareableResource runs
8
8
 
9
+ function parseJsonRecord(raw: string | null): Record<string, unknown> | null {
10
+ if (!raw) return null;
11
+ try {
12
+ const parsed = JSON.parse(raw) as unknown;
13
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
14
+ ? (parsed as Record<string, unknown>)
15
+ : null;
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+
9
21
  export default defineAction({
10
22
  description:
11
23
  "List all design states, fixtures, and captures for a design. " +
@@ -52,6 +64,8 @@ export default defineAction({
52
64
  kind: schema.designState.kind,
53
65
  breakpoint: schema.designState.breakpoint,
54
66
  route: schema.designState.route,
67
+ fixtureData: schema.designState.fixtureData,
68
+ captureData: schema.designState.captureData,
55
69
  previewRef: schema.designState.previewRef,
56
70
  createdAt: schema.designState.createdAt,
57
71
  updatedAt: schema.designState.updatedAt,
@@ -66,7 +80,11 @@ export default defineAction({
66
80
 
67
81
  return {
68
82
  count: rows.length,
69
- states: rows,
83
+ states: rows.map((row) => ({
84
+ ...row,
85
+ fixtureData: parseJsonRecord(row.fixtureData),
86
+ captureData: parseJsonRecord(row.captureData),
87
+ })),
70
88
  };
71
89
  },
72
90
  });
@@ -0,0 +1,292 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { z } from "zod";
3
+
4
+ import { executeProviderApiRequest } from "../server/lib/provider-api.js";
5
+
6
+ const FIGMA_FILE_KEY_RE = /^[A-Za-z0-9_-]{8,}$/;
7
+
8
+ const FigmaRenderFormatSchema = z.enum(["svg", "png"]).default("svg");
9
+
10
+ const schemaInput = z
11
+ .object({
12
+ fileUrl: z
13
+ .string()
14
+ .trim()
15
+ .optional()
16
+ .describe(
17
+ "Figma file URL, for example https://www.figma.com/design/<fileKey>/...",
18
+ ),
19
+ fileKey: z
20
+ .string()
21
+ .trim()
22
+ .optional()
23
+ .describe("Figma file key. Used when fileUrl is omitted."),
24
+ query: z
25
+ .string()
26
+ .trim()
27
+ .optional()
28
+ .describe("Optional case-insensitive search over names/descriptions."),
29
+ limit: z.coerce
30
+ .number()
31
+ .int()
32
+ .min(1)
33
+ .max(64)
34
+ .default(48)
35
+ .describe("Maximum number of Figma library assets to return."),
36
+ renderFormat: FigmaRenderFormatSchema.describe(
37
+ "Image format for insertable render URLs.",
38
+ ),
39
+ })
40
+ .refine((value) => value.fileUrl || value.fileKey, {
41
+ message: "Pass fileUrl or fileKey.",
42
+ path: ["fileUrl"],
43
+ });
44
+
45
+ type FigmaProviderEnvelope = {
46
+ response?: {
47
+ ok?: boolean;
48
+ status?: number;
49
+ statusText?: string;
50
+ json?: unknown;
51
+ text?: string;
52
+ };
53
+ };
54
+
55
+ type FigmaAssetKind = "component" | "component_set";
56
+
57
+ type FigmaComponentRecord = {
58
+ key?: unknown;
59
+ file_key?: unknown;
60
+ node_id?: unknown;
61
+ name?: unknown;
62
+ description?: unknown;
63
+ thumbnail_url?: unknown;
64
+ created_at?: unknown;
65
+ updated_at?: unknown;
66
+ containing_frame?: unknown;
67
+ user?: unknown;
68
+ };
69
+
70
+ type FigmaImageResponse = {
71
+ images?: Record<string, string | null | undefined>;
72
+ };
73
+
74
+ function stringValue(value: unknown): string | null {
75
+ return typeof value === "string" && value.trim() ? value.trim() : null;
76
+ }
77
+
78
+ function numberOrDefault(value: number | undefined, fallback: number): number {
79
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
80
+ }
81
+
82
+ export function parseFigmaFileKey(input: string | undefined): string | null {
83
+ const value = input?.trim();
84
+ if (!value) return null;
85
+ if (FIGMA_FILE_KEY_RE.test(value)) return value;
86
+
87
+ try {
88
+ const url = new URL(value);
89
+ const parts = url.pathname.split("/").filter(Boolean);
90
+ for (const segment of ["design", "file", "proto"]) {
91
+ const index = parts.indexOf(segment);
92
+ if (index >= 0) {
93
+ const candidate = parts[index + 1];
94
+ if (candidate && FIGMA_FILE_KEY_RE.test(candidate)) return candidate;
95
+ }
96
+ }
97
+ } catch {
98
+ return null;
99
+ }
100
+
101
+ return null;
102
+ }
103
+
104
+ function figmaNodeUrl(
105
+ fileKey: string,
106
+ nodeId: string,
107
+ fileUrl?: string,
108
+ ): string {
109
+ const nodeParam = nodeId.replace(/:/g, "-");
110
+ try {
111
+ const url = new URL(fileUrl || `https://www.figma.com/design/${fileKey}`);
112
+ url.searchParams.set("node-id", nodeParam);
113
+ return url.href;
114
+ } catch {
115
+ return `https://www.figma.com/design/${fileKey}?node-id=${encodeURIComponent(
116
+ nodeParam,
117
+ )}`;
118
+ }
119
+ }
120
+
121
+ function recordSearchText(record: FigmaComponentRecord): string {
122
+ const frame =
123
+ record.containing_frame &&
124
+ typeof record.containing_frame === "object" &&
125
+ "name" in record.containing_frame
126
+ ? stringValue((record.containing_frame as { name?: unknown }).name)
127
+ : null;
128
+ return [stringValue(record.name), stringValue(record.description), frame]
129
+ .filter(Boolean)
130
+ .join(" ")
131
+ .toLowerCase();
132
+ }
133
+
134
+ function matchesQuery(record: FigmaComponentRecord, query: string | undefined) {
135
+ const normalized = query?.trim().toLowerCase();
136
+ if (!normalized) return true;
137
+ return recordSearchText(record).includes(normalized);
138
+ }
139
+
140
+ function normalizeFigmaRecords(
141
+ value: unknown,
142
+ kind: FigmaAssetKind,
143
+ ): FigmaComponentRecord[] {
144
+ if (!value || typeof value !== "object") return [];
145
+ const meta = (value as { meta?: unknown }).meta;
146
+ const key = kind === "component" ? "components" : "component_sets";
147
+ const records =
148
+ meta && typeof meta === "object"
149
+ ? (meta as Record<string, unknown>)[key]
150
+ : undefined;
151
+ if (!Array.isArray(records)) return [];
152
+ return records.filter(
153
+ (record): record is FigmaComponentRecord =>
154
+ !!record && typeof record === "object",
155
+ );
156
+ }
157
+
158
+ function providerJson(envelope: unknown, label: string): unknown {
159
+ const response = (envelope as FigmaProviderEnvelope | null)?.response;
160
+ if (!response) throw new Error(`Figma ${label} response was empty.`);
161
+ if (response.ok === false) {
162
+ const detail =
163
+ stringValue(response.text) ||
164
+ response.statusText ||
165
+ `HTTP ${response.status ?? "error"}`;
166
+ throw new Error(`Figma ${label} request failed: ${detail}`);
167
+ }
168
+ return response.json;
169
+ }
170
+
171
+ async function figmaGet(path: string, query?: Record<string, unknown>) {
172
+ return executeProviderApiRequest({
173
+ provider: "figma",
174
+ method: "GET",
175
+ path,
176
+ query,
177
+ maxBytes: 2 * 1024 * 1024,
178
+ });
179
+ }
180
+
181
+ export default defineAction({
182
+ description:
183
+ "List reusable assets from a Figma file for the Design asset panel. Returns components and component sets with thumbnails, insertable render URLs, and source provenance. Requires a saved FIGMA_ACCESS_TOKEN secret.",
184
+ schema: schemaInput,
185
+ http: { method: "GET" },
186
+ readOnly: true,
187
+ publicAgent: { expose: true, readOnly: true, requiresAuth: true },
188
+ run: async (args) => {
189
+ const fileKey =
190
+ parseFigmaFileKey(args.fileKey) ?? parseFigmaFileKey(args.fileUrl);
191
+ if (!fileKey) {
192
+ throw new Error("Could not find a Figma file key in the provided URL.");
193
+ }
194
+
195
+ const limit = numberOrDefault(args.limit, 48);
196
+ const [componentsEnvelope, componentSetsEnvelope] = await Promise.all([
197
+ figmaGet(`/files/${fileKey}/components`),
198
+ figmaGet(`/files/${fileKey}/component_sets`),
199
+ ]);
200
+ const componentRecords = normalizeFigmaRecords(
201
+ providerJson(componentsEnvelope, "components"),
202
+ "component",
203
+ )
204
+ .filter((record) => matchesQuery(record, args.query))
205
+ .map((record) => ({ kind: "component" as const, record }));
206
+ const componentSetRecords = normalizeFigmaRecords(
207
+ providerJson(componentSetsEnvelope, "component sets"),
208
+ "component_set",
209
+ )
210
+ .filter((record) => matchesQuery(record, args.query))
211
+ .map((record) => ({ kind: "component_set" as const, record }));
212
+
213
+ const rawItems = [...componentRecords, ...componentSetRecords].slice(
214
+ 0,
215
+ limit,
216
+ );
217
+ const renderIds = rawItems
218
+ .map((item) => stringValue(item.record.node_id))
219
+ .filter((id): id is string => !!id);
220
+ let rendered: Record<string, string | null | undefined> = {};
221
+ if (renderIds.length > 0) {
222
+ const imagesEnvelope = await figmaGet(`/images/${fileKey}`, {
223
+ ids: renderIds.join(","),
224
+ format: args.renderFormat,
225
+ svg_include_id: true,
226
+ });
227
+ const images = providerJson(
228
+ imagesEnvelope,
229
+ "images",
230
+ ) as FigmaImageResponse;
231
+ rendered = images.images ?? {};
232
+ }
233
+
234
+ const assets = rawItems
235
+ .map(({ kind, record }) => {
236
+ const nodeId = stringValue(record.node_id);
237
+ const key = stringValue(record.key);
238
+ const name = stringValue(record.name) ?? "Untitled Figma asset";
239
+ const description = stringValue(record.description);
240
+ const thumbnailUrl = stringValue(record.thumbnail_url);
241
+ const renderUrl = nodeId ? stringValue(rendered[nodeId]) : null;
242
+ const containingFrame =
243
+ record.containing_frame && typeof record.containing_frame === "object"
244
+ ? {
245
+ name:
246
+ stringValue(
247
+ (record.containing_frame as { name?: unknown }).name,
248
+ ) ?? null,
249
+ nodeId:
250
+ stringValue(
251
+ (record.containing_frame as { nodeId?: unknown }).nodeId,
252
+ ) ??
253
+ stringValue(
254
+ (record.containing_frame as { node_id?: unknown }).node_id,
255
+ ),
256
+ }
257
+ : null;
258
+
259
+ return {
260
+ id: `figma:${fileKey}:${nodeId ?? key ?? name}`,
261
+ kind,
262
+ fileKey,
263
+ nodeId,
264
+ componentKey: key,
265
+ name,
266
+ description,
267
+ thumbnailUrl,
268
+ renderUrl,
269
+ insertUrl: renderUrl ?? thumbnailUrl,
270
+ sourceUrl: nodeId
271
+ ? figmaNodeUrl(fileKey, nodeId, args.fileUrl)
272
+ : null,
273
+ containingFrame,
274
+ createdAt: stringValue(record.created_at),
275
+ updatedAt: stringValue(record.updated_at),
276
+ };
277
+ })
278
+ .filter((asset) => asset.nodeId || asset.componentKey);
279
+
280
+ return {
281
+ source: "figma",
282
+ fileKey,
283
+ query: args.query ?? null,
284
+ renderFormat: args.renderFormat,
285
+ total: componentRecords.length + componentSetRecords.length,
286
+ returned: assets.length,
287
+ assets,
288
+ guidance:
289
+ "Use renderUrl/insertUrl with insert-asset and preserve fileKey, nodeId, componentKey, and sourceUrl for provenance. Components/component sets are reusable Figma library assets; styles and variables belong in design-system token sync.",
290
+ };
291
+ },
292
+ });
@@ -0,0 +1,238 @@
1
+ /**
2
+ * migrate-board-objects-to-file
3
+ *
4
+ * Lazy, idempotent migration that converts the legacy designs.data.boardObjects
5
+ * JSON blob into the new board file architecture.
6
+ *
7
+ * ## What it does
8
+ *
9
+ * 1. Guards on boardFileId already set — returns early if the migration has
10
+ * already run (idempotent).
11
+ * 2. Reads designs.data.boardObjects and converts each entry into an HTML
12
+ * fragment via boardObjectEntryToHtmlFragment, preserving negative left/top
13
+ * coordinates exactly (no clamping).
14
+ * 3. Inserts the fragments as direct <body> children of a new __board__.html
15
+ * design file (or merges into an existing one if somehow already present).
16
+ * 4. Stores the new file's id into designs.data.boardFileId in a transaction.
17
+ * 5. Nulls out designs.data.boardObjects so the old model no longer conflicts.
18
+ *
19
+ * ## Contract
20
+ *
21
+ * - Requires editor access on the design.
22
+ * - SCHEMA ADDITIVE ONLY — no columns are dropped or renamed; only designs.data
23
+ * JSON keys change.
24
+ * - Safe to call on designs with no board objects: creates the board file with
25
+ * an empty body and sets boardFileId.
26
+ *
27
+ * ## Trigger
28
+ *
29
+ * Called by DesignEditor on design open when designs.data.boardFileId is absent.
30
+ */
31
+
32
+ import { defineAction } from "@agent-native/core";
33
+ import { seedFromText } from "@agent-native/core/collab";
34
+ import { assertAccess } from "@agent-native/core/sharing";
35
+ import { and, eq } from "drizzle-orm";
36
+ import { nanoid } from "nanoid";
37
+ import { z } from "zod";
38
+
39
+ import { getDb, schema } from "../server/db/index.js";
40
+ import {
41
+ BOARD_FILENAME,
42
+ boardObjectEntryToHtmlFragment,
43
+ emptyBoardHtml,
44
+ } from "../shared/board-file.js";
45
+ import { parseBoardObjects } from "../shared/board-objects.js";
46
+
47
+ export default defineAction({
48
+ description:
49
+ "Migrate the legacy boardObjects blob (designs.data.boardObjects) into the " +
50
+ "new board file architecture. Creates a __board__.html design file, writes " +
51
+ "each board object as an absolute-positioned HTML element (preserving " +
52
+ "negative coordinates), stores boardFileId in designs.data, and nulls " +
53
+ "boardObjects. Idempotent — returns immediately if boardFileId is already " +
54
+ "set. Requires editor access on the design.",
55
+ schema: z.object({
56
+ designId: z
57
+ .string()
58
+ .describe("Design project ID to migrate board objects for."),
59
+ }),
60
+ run: async ({ designId }) => {
61
+ await assertAccess("design", designId, "editor");
62
+
63
+ const db = getDb();
64
+
65
+ // ── 1. Idempotency guard ──────────────────────────────────────────────
66
+ // Read the design data JSON to check whether boardFileId is already set.
67
+ // guard:allow-unscoped — assertAccess "editor" above scopes this read to
68
+ // the requesting user's accessible designs.
69
+ const [existing] = await db
70
+ .select({ data: schema.designs.data })
71
+ .from(schema.designs)
72
+ .where(eq(schema.designs.id, designId))
73
+ .limit(1);
74
+
75
+ if (!existing) {
76
+ throw new Error(`Design "${designId}" not found.`);
77
+ }
78
+
79
+ let parsed: Record<string, unknown>;
80
+ try {
81
+ const raw = JSON.parse(existing.data);
82
+ parsed =
83
+ raw && typeof raw === "object" && !Array.isArray(raw)
84
+ ? (raw as Record<string, unknown>)
85
+ : {};
86
+ } catch {
87
+ parsed = {};
88
+ }
89
+
90
+ if (
91
+ typeof parsed["boardFileId"] === "string" &&
92
+ parsed["boardFileId"].length > 0
93
+ ) {
94
+ return {
95
+ designId,
96
+ migrated: false,
97
+ boardFileId: parsed["boardFileId"] as string,
98
+ reason: "boardFileId already set — migration already complete.",
99
+ };
100
+ }
101
+
102
+ // ── 2. Parse legacy board objects ────────────────────────────────────
103
+ const boardObjects = parseBoardObjects(parsed["boardObjects"]);
104
+ const entries = Object.values(boardObjects);
105
+
106
+ // ── 3. Build the board HTML ───────────────────────────────────────────
107
+ // Start from the canonical empty-board template and inject each entry as
108
+ // a direct <body> child. Negative left/top are preserved — the migration
109
+ // intentionally does NOT clamp coords (appendCanvasPrimitiveToHtml clamps
110
+ // to x/y >= 0 for new screen primitives; the board surface has no such
111
+ // restriction).
112
+ let boardHtml = emptyBoardHtml();
113
+ if (entries.length > 0) {
114
+ const fragments = entries
115
+ .sort((a, b) => {
116
+ // Stable render order: lower z first, then creation order.
117
+ const az = a.geometry.z ?? 0;
118
+ const bz = b.geometry.z ?? 0;
119
+ if (az !== bz) return az - bz;
120
+ return a.createdAt < b.createdAt ? -1 : 1;
121
+ })
122
+ .map((entry) => boardObjectEntryToHtmlFragment(entry))
123
+ .join("\n");
124
+
125
+ // Insert fragments before </body>.
126
+ boardHtml = boardHtml.replace("</body>", `${fragments}\n</body>`);
127
+ }
128
+
129
+ const now = new Date().toISOString();
130
+
131
+ // ── 4. Upsert the board file and update designs.data atomically ───────
132
+ // Run in a transaction so concurrent opens cannot create two board files.
133
+ let boardFileId = "";
134
+
135
+ await db.transaction(async (tx) => {
136
+ // Re-read designs.data inside the transaction to guard against a
137
+ // concurrent migration that raced us.
138
+ const [fresh] = await tx
139
+ .select({ data: schema.designs.data })
140
+ .from(schema.designs)
141
+ .where(eq(schema.designs.id, designId))
142
+ .limit(1);
143
+
144
+ if (!fresh) {
145
+ throw new Error(`Design "${designId}" not found.`);
146
+ }
147
+
148
+ let freshParsed: Record<string, unknown>;
149
+ try {
150
+ const raw = JSON.parse(fresh.data);
151
+ freshParsed =
152
+ raw && typeof raw === "object" && !Array.isArray(raw)
153
+ ? (raw as Record<string, unknown>)
154
+ : {};
155
+ } catch {
156
+ freshParsed = {};
157
+ }
158
+
159
+ // Double-check inside the transaction.
160
+ if (
161
+ typeof freshParsed["boardFileId"] === "string" &&
162
+ freshParsed["boardFileId"].length > 0
163
+ ) {
164
+ boardFileId = freshParsed["boardFileId"] as string;
165
+ return; // Another concurrent call already finished — skip.
166
+ }
167
+
168
+ // Check if a __board__.html file was somehow already created (shouldn't
169
+ // happen in normal flow, but be defensive).
170
+ const [existingBoardFile] = await tx
171
+ .select({ id: schema.designFiles.id })
172
+ .from(schema.designFiles)
173
+ .where(
174
+ and(
175
+ eq(schema.designFiles.designId, designId),
176
+ eq(schema.designFiles.filename, BOARD_FILENAME),
177
+ ),
178
+ )
179
+ .limit(1);
180
+
181
+ if (existingBoardFile) {
182
+ // Board file exists but boardFileId was not set — link it and update
183
+ // its content.
184
+ boardFileId = existingBoardFile.id;
185
+ await tx
186
+ .update(schema.designFiles)
187
+ .set({ content: boardHtml, updatedAt: now })
188
+ .where(eq(schema.designFiles.id, boardFileId));
189
+ } else {
190
+ // Create the board file.
191
+ boardFileId = nanoid();
192
+ await tx.insert(schema.designFiles).values({
193
+ id: boardFileId,
194
+ designId,
195
+ filename: BOARD_FILENAME,
196
+ fileType: "html",
197
+ content: boardHtml,
198
+ createdAt: now,
199
+ updatedAt: now,
200
+ });
201
+ }
202
+
203
+ // Write boardFileId into designs.data; null out boardObjects.
204
+ const nextData: Record<string, unknown> = {
205
+ ...freshParsed,
206
+ boardFileId,
207
+ boardObjects: null,
208
+ };
209
+
210
+ await tx
211
+ .update(schema.designs)
212
+ .set({
213
+ data: JSON.stringify(nextData),
214
+ updatedAt: now,
215
+ })
216
+ .where(eq(schema.designs.id, designId));
217
+ });
218
+
219
+ // ── 5. Seed collab state for the new board file ───────────────────────
220
+ // (Best-effort: collab seeding after the file row is committed.)
221
+ try {
222
+ await seedFromText(boardFileId, boardHtml);
223
+ } catch {
224
+ // Non-fatal — the board file still renders from SQL content.
225
+ }
226
+
227
+ return {
228
+ designId,
229
+ migrated: true,
230
+ boardFileId,
231
+ migratedObjectCount: entries.length,
232
+ message:
233
+ entries.length > 0
234
+ ? `Migrated ${entries.length} board object(s) to board file ${boardFileId}.`
235
+ : `Created empty board file ${boardFileId} (no legacy board objects).`,
236
+ };
237
+ },
238
+ });
@@ -18,7 +18,8 @@
18
18
  * --view View name (list, editor, design-systems, present, templates, settings)
19
19
  * --designId Design ID (for editor/present views)
20
20
  * --editorView Editor mode for designs: single or overview
21
- * --inspectorTab Inspector tab for designs: design, tweaks, or extensions
21
+ * --inspectorTab Inspector tab for designs: design or tweaks (extensions opens Tools for compatibility)
22
+ * --leftPanel Left editor panel: file, agent, assets, tools, or tokens
22
23
  * --fileId Screen/file id to focus in the design editor
23
24
  * --filename Screen filename to focus in the design editor
24
25
  * --tool Design editor tool to activate
@@ -47,9 +48,17 @@ const designEditorToolSchema = z.enum([
47
48
  "scale",
48
49
  ]);
49
50
 
51
+ const designLeftPanelSchema = z.enum([
52
+ "file",
53
+ "agent",
54
+ "assets",
55
+ "tools",
56
+ "tokens",
57
+ ]);
58
+
50
59
  export default defineAction({
51
60
  description:
52
- "Navigate the UI to a specific view or path. Views: list, editor, design-systems, present, templates, settings. Use --designId with editor/present views and --designSystemId with design-systems. For designs, use editorView=overview to show the infinite screens canvas, or editorView=single with fileId/filename/screen to focus a screen. Use inspectorTab=extensions to focus the in-editor extension panel. Use tool to activate a design editor tool.",
61
+ "Navigate the UI to a specific view or path. Views: list, editor, design-systems, present, templates, settings. Use --designId with editor/present views and --designSystemId with design-systems. For designs, use editorView=overview to show the infinite screens canvas, or editorView=single with fileId/filename/screen to focus a screen. Use leftPanel=file|agent|assets|tools|tokens to focus the Figma-style left rail. Legacy inspectorTab=extensions opens Tools. Use tool to activate a design editor tool.",
53
62
  schema: z
54
63
  .object({
55
64
  view: z
@@ -83,6 +92,10 @@ export default defineAction({
83
92
  .enum(["design", "tweaks", "extensions"])
84
93
  .optional()
85
94
  .describe("Alias for inspectorTab"),
95
+ leftPanel: designLeftPanelSchema
96
+ .optional()
97
+ .describe("Design editor left rail panel to focus"),
98
+ panel: designLeftPanelSchema.optional().describe("Alias for leftPanel"),
86
99
  fileId: z.string().optional().describe("Design file/screen ID to focus"),
87
100
  screenId: z.string().optional().describe("Alias for fileId"),
88
101
  filename: z
@@ -146,6 +159,8 @@ export default defineAction({
146
159
  if (editorView) nav.editorView = editorView;
147
160
  const inspectorTab = args.inspectorTab ?? args.inspector;
148
161
  if (inspectorTab) nav.inspectorTab = inspectorTab;
162
+ const leftPanel = args.leftPanel ?? args.panel;
163
+ if (leftPanel) nav.leftPanel = leftPanel;
149
164
  if (args.fileId) nav.fileId = args.fileId;
150
165
  if (args.screenId) nav.screenId = args.screenId;
151
166
  if (args.filename) nav.filename = args.filename;
@@ -159,7 +174,7 @@ export default defineAction({
159
174
  args.designId ? ` (design: ${args.designId})` : ""
160
175
  }${editorView ? ` (${editorView} view)` : ""}${
161
176
  inspectorTab ? ` (${inspectorTab} inspector)` : ""
162
- }${
177
+ }${leftPanel ? ` (${leftPanel} panel)` : ""}${
163
178
  args.fileId || args.screenId || args.filename || args.screen
164
179
  ? ` (screen: ${args.fileId ?? args.screenId ?? args.filename ?? args.screen})`
165
180
  : ""
@@ -36,7 +36,10 @@ import "../server/db/index.js"; // ensure registerShareableResource runs
36
36
  import { resolveSourceCapabilities } from "../shared/capability-resolver.js";
37
37
  import { buildCodeLayerProjection } from "../shared/code-layer.js";
38
38
  import type { CodeLayerSource } from "../shared/code-layer.js";
39
- import { componentNameFor } from "../shared/component-model.js";
39
+ import {
40
+ componentNameFor,
41
+ componentNodeIdMatches,
42
+ } from "../shared/component-model.js";
40
43
  import { hasCapability } from "../shared/design-source-capabilities.js";
41
44
  import { normalizeDesignSourceType } from "../shared/source-mode.js";
42
45
 
@@ -148,7 +151,9 @@ export default defineAction({
148
151
  source: codeLayerSource,
149
152
  });
150
153
 
151
- const node = projection.nodes.find((n) => n.id === nodeId);
154
+ const node = projection.nodes.find((n) =>
155
+ componentNodeIdMatches(n, nodeId),
156
+ );
152
157
  if (!node) {
153
158
  throw new Error(
154
159
  `Node "${nodeId}" not found. Run get-code-layer-projection to list current ids.`,