@agent-native/core 0.80.2 → 0.80.5

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 (322) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +66 -2
  3. package/corpus/core/docs/content/locales/ar-SA/real-time-collaboration.mdx +2 -2
  4. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +1 -1
  5. package/corpus/core/docs/content/locales/de-DE/real-time-collaboration.mdx +3 -3
  6. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +1 -1
  7. package/corpus/core/docs/content/locales/es-ES/real-time-collaboration.mdx +2 -2
  8. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +1 -1
  9. package/corpus/core/docs/content/locales/fr-FR/real-time-collaboration.mdx +2 -2
  10. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +1 -1
  11. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +1 -1
  12. package/corpus/core/docs/content/locales/ja-JP/real-time-collaboration.mdx +2 -2
  13. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +1 -1
  14. package/corpus/core/docs/content/locales/ko-KR/real-time-collaboration.mdx +2 -2
  15. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +1 -1
  16. package/corpus/core/docs/content/locales/pt-BR/real-time-collaboration.mdx +2 -2
  17. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +1 -1
  18. package/corpus/core/docs/content/locales/zh-CN/real-time-collaboration.mdx +2 -2
  19. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +1 -1
  20. package/corpus/core/docs/content/locales/zh-TW/real-time-collaboration.mdx +2 -2
  21. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +1 -1
  22. package/corpus/core/docs/content/real-time-collaboration.mdx +2 -2
  23. package/corpus/core/docs/content/template-design.mdx +1 -1
  24. package/corpus/core/package.json +3 -7
  25. package/corpus/core/src/action.ts +32 -0
  26. package/corpus/core/src/agent/durable-background.ts +32 -11
  27. package/corpus/core/src/agent/production-agent.ts +14 -1
  28. package/corpus/core/src/agent/run-loop-with-resume.ts +3 -1
  29. package/corpus/core/src/brand-kit/fig/index.ts +17 -37
  30. package/corpus/core/src/cli/index.ts +1 -1
  31. package/corpus/core/src/cli/mcp-config-writers.ts +187 -51
  32. package/corpus/core/src/cli/plan-local.ts +445 -108
  33. package/corpus/core/src/cli/skills.ts +32 -8
  34. package/corpus/core/src/client/AssistantChat.tsx +11 -1
  35. package/corpus/core/src/client/agent-chat.ts +12 -1
  36. package/corpus/core/src/client/composer/PromptComposer.tsx +100 -41
  37. package/corpus/core/src/client/org/OrgSwitcher.tsx +1 -1
  38. package/corpus/core/src/client/sharing/ShareButton.tsx +3 -27
  39. package/corpus/core/src/collab/presence.ts +1 -1
  40. package/corpus/core/src/mcp/connect-route.ts +34 -5
  41. package/corpus/core/src/server/action-discovery.ts +4 -0
  42. package/corpus/core/src/server/action-routes.ts +14 -0
  43. package/corpus/core/src/server/agent-chat-plugin.ts +20 -0
  44. package/corpus/core/src/vite/client.ts +15 -1
  45. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +2 -0
  46. package/corpus/templates/analytics/actions/update-dashboard.ts +8 -0
  47. package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -1
  48. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +117 -33
  49. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +4 -0
  50. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +7 -0
  51. package/corpus/templates/analytics/changelog/2026-06-27-dashboards-can-now-nest-under-a-parent-in-the-sidebar-via-a-.md +6 -0
  52. package/corpus/templates/analytics/changelog/2026-06-29-analytics-pages-now-sit-closer-under-the-header-with-tighter.md +6 -0
  53. package/corpus/templates/assets/actions/generate-image-batch.ts +3 -0
  54. package/corpus/templates/assets/actions/generate-image.ts +3 -0
  55. package/corpus/templates/assets/changelog/2026-06-29-longer-agent-generation-window.md +6 -0
  56. package/corpus/templates/assets/netlify.toml +3 -3
  57. package/corpus/templates/assets/server/plugins/agent-chat.ts +4 -1
  58. package/corpus/templates/clips/app/components/capture-install-options.tsx +7 -8
  59. package/corpus/templates/clips/app/components/player/timestamped-comment-button.tsx +162 -0
  60. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +1 -1
  61. package/corpus/templates/clips/app/i18n/ar-SA.ts +4 -0
  62. package/corpus/templates/clips/app/i18n/de-DE.ts +4 -0
  63. package/corpus/templates/clips/app/i18n/en-US.ts +4 -0
  64. package/corpus/templates/clips/app/i18n/es-ES.ts +4 -0
  65. package/corpus/templates/clips/app/i18n/fr-FR.ts +4 -0
  66. package/corpus/templates/clips/app/i18n/hi-IN.ts +4 -0
  67. package/corpus/templates/clips/app/i18n/ja-JP.ts +4 -0
  68. package/corpus/templates/clips/app/i18n/ko-KR.ts +4 -0
  69. package/corpus/templates/clips/app/i18n/pt-BR.ts +4 -0
  70. package/corpus/templates/clips/app/i18n/zh-CN.ts +4 -0
  71. package/corpus/templates/clips/app/i18n/zh-TW.ts +4 -0
  72. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +42 -18
  73. package/corpus/templates/content/AGENTS.md +12 -0
  74. package/corpus/templates/content/app/blocks/BuilderDocsBlocks.tsx +10 -0
  75. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +1 -0
  76. package/corpus/templates/content/app/components/editor/ContentReferencePreview.tsx +185 -0
  77. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +3 -0
  78. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +19 -0
  79. package/corpus/templates/content/app/components/editor/extensions/ContentReferenceNode.tsx +82 -0
  80. package/corpus/templates/content/app/global.css +9 -0
  81. package/corpus/templates/content/app/i18n/zh-TW.ts +12 -0
  82. package/corpus/templates/content/app/i18n-data.ts +140 -0
  83. package/corpus/templates/content/changelog/2026-06-29-reusable-mdx-references-now-preview-linked-local-documents-i.md +6 -0
  84. package/corpus/templates/content/shared/builder-docs-blocks.ts +8 -0
  85. package/corpus/templates/content/shared/builder-mdx.ts +141 -4
  86. package/corpus/templates/content/shared/nfm.ts +62 -0
  87. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +10 -11
  88. package/corpus/templates/design/AGENTS.md +5 -0
  89. package/corpus/templates/design/_gitignore +7 -0
  90. package/corpus/templates/design/actions/apply-visual-edit.ts +22 -12
  91. package/corpus/templates/design/actions/create-design-system.ts +15 -4
  92. package/corpus/templates/design/actions/create-file.ts +20 -1
  93. package/corpus/templates/design/actions/delete-design-system.ts +2 -2
  94. package/corpus/templates/design/actions/duplicate-design.ts +35 -9
  95. package/corpus/templates/design/actions/generate-design.ts +146 -69
  96. package/corpus/templates/design/actions/generate-screens.ts +51 -19
  97. package/corpus/templates/design/actions/get-design-snapshot.ts +5 -1
  98. package/corpus/templates/design/actions/get-design.ts +2 -0
  99. package/corpus/templates/design/actions/import-design-project.ts +28 -9
  100. package/corpus/templates/design/actions/import-document.ts +22 -8
  101. package/corpus/templates/design/actions/index-design-system-with-builder.ts +6 -153
  102. package/corpus/templates/design/actions/navigate.ts +105 -54
  103. package/corpus/templates/design/actions/set-default-design-system.ts +17 -3
  104. package/corpus/templates/design/actions/show-design-questions.ts +2 -0
  105. package/corpus/templates/design/actions/update-design.ts +2 -1
  106. package/corpus/templates/design/actions/view-screen.ts +12 -6
  107. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +4 -4
  108. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +159 -41
  109. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +8 -4
  110. package/corpus/templates/design/app/components/design/DrawOverlay.tsx +37 -42
  111. package/corpus/templates/design/app/components/design/EditPanel.tsx +336 -183
  112. package/corpus/templates/design/app/components/design/LayersPanel.tsx +153 -34
  113. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +135 -33
  114. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +6 -0
  115. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +2 -1
  116. package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +4 -10
  117. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +84 -43
  118. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +4 -4
  119. package/corpus/templates/design/app/components/design/inspector/{FigmaColorPicker.tsx → DesignColorPicker.tsx} +89 -62
  120. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +14 -26
  121. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +40 -12
  122. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +13 -4
  123. package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +13 -13
  124. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +13 -9
  125. package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +13 -4
  126. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +6 -6
  127. package/corpus/templates/design/app/components/design/inspector/{figma-icons.tsx → design-icons.tsx} +2 -2
  128. package/corpus/templates/design/app/components/design/inspector/index.ts +12 -12
  129. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +5 -2
  130. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +3 -3
  131. package/corpus/templates/design/app/components/layout/Layout.tsx +5 -2
  132. package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
  133. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +167 -91
  134. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +16 -3
  135. package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +15 -0
  136. package/corpus/templates/design/app/global.css +4 -0
  137. package/corpus/templates/design/app/hooks/use-design-system.ts +4 -1
  138. package/corpus/templates/design/app/hooks/use-navigation-state.ts +38 -3
  139. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +9 -2
  140. package/corpus/templates/design/app/i18n/zh-TW.ts +22 -0
  141. package/corpus/templates/design/app/i18n-data.ts +527 -0
  142. package/corpus/templates/design/app/lib/design-systems.ts +6 -4
  143. package/corpus/templates/design/app/lib/design-templates.ts +15 -3
  144. package/corpus/templates/design/app/pages/DesignEditor.tsx +641 -158
  145. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +42 -34
  146. package/corpus/templates/design/app/pages/VisualEdit.tsx +100 -0
  147. package/corpus/templates/design/app/root.tsx +42 -9
  148. package/corpus/templates/design/app/routes/visual-edit.tsx +7 -0
  149. package/corpus/templates/design/changelog/2026-06-28-live-cursors-now-keep-names-beside-a-cleaner-pointer-marker.md +1 -1
  150. package/corpus/templates/design/changelog/2026-06-28-mode-switching-now-lives-in-a-compact-bottom-right-icon-dock.md +1 -1
  151. package/corpus/templates/design/changelog/2026-06-28-visual-code-design-mode.md +6 -0
  152. package/corpus/templates/design/changelog/2026-06-29-design-generation-prompts-now-show-connect-ai-before-sending.md +6 -0
  153. package/corpus/templates/design/changelog/2026-06-29-design-mode-top-chrome-now-groups-collaborators-preview-shar.md +1 -1
  154. package/corpus/templates/design/changelog/2026-06-29-fixed-design-editor-exports-and-gradient-edits-so-multi-scre.md +6 -0
  155. package/corpus/templates/design/changelog/2026-06-29-longer-hosted-agent-turns.md +6 -0
  156. package/corpus/templates/design/changelog/2026-06-29-png-export-handles-modern-css-color-functions-more-reliably.md +6 -0
  157. package/corpus/templates/design/changelog/2026-06-29-png-export-now-ignores-duplicate-clicks-while-a-download-is-.md +6 -0
  158. package/corpus/templates/design/changelog/2026-06-29-public-visual-edit-and-shared-design-links-now-invite-visito.md +6 -0
  159. package/corpus/templates/design/changelog/2026-06-29-visual-edit-actions-now-reject-missing-design-targets-before.md +6 -0
  160. package/corpus/templates/design/e2e/README.md +71 -0
  161. package/corpus/templates/design/e2e/global-setup.ts +157 -0
  162. package/corpus/templates/design/e2e/helpers.ts +148 -0
  163. package/corpus/templates/design/netlify.toml +3 -4
  164. package/corpus/templates/design/package.json +5 -1
  165. package/corpus/templates/design/playwright.config.ts +51 -0
  166. package/corpus/templates/design/server/handlers/index-design-system-with-builder.ts +121 -0
  167. package/corpus/templates/design/server/handlers/uploads.ts +89 -38
  168. package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +181 -0
  169. package/corpus/templates/design/server/lib/coding-handoff.ts +33 -4
  170. package/corpus/templates/design/server/lib/design-export.ts +35 -8
  171. package/corpus/templates/design/server/plugins/agent-chat.ts +4 -0
  172. package/corpus/templates/design/server/plugins/auth.ts +10 -1
  173. package/corpus/templates/design/server/routes/api/index-design-system-with-builder.post.ts +1 -0
  174. package/corpus/templates/design/shared/api.ts +2 -0
  175. package/corpus/templates/design/shared/apply-edits.ts +12 -8
  176. package/corpus/templates/design/shared/canvas-math.ts +19 -1
  177. package/corpus/templates/design/shared/code-layer.ts +87 -28
  178. package/corpus/templates/design/shared/generation-session.ts +1 -0
  179. package/corpus/templates/design/shared/resolve-tweaks.ts +4 -3
  180. package/corpus/templates/design/shared/shader-presets.ts +10 -0
  181. package/corpus/templates/design/shared/source-mode.ts +9 -0
  182. package/corpus/templates/design/vitest.config.ts +2 -1
  183. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +1 -1
  184. package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +1 -1
  185. package/corpus/templates/plan/.agents/skills/visual-plan/references/local-files.md +14 -5
  186. package/corpus/templates/plan/.agents/skills/visual-recap/references/local-files.md +14 -5
  187. package/corpus/templates/plan/AGENTS.md +1 -1
  188. package/corpus/templates/plan/actions/create-plan-design.ts +1 -1
  189. package/corpus/templates/plan/actions/validate-local-plan-source.ts +110 -0
  190. package/corpus/templates/plan/app/pages/PlansPage.tsx +1 -0
  191. package/corpus/templates/plan/docs/commenting-ux-plan.md +2 -2
  192. package/corpus/templates/plan/server/lib/plan-connector-catalog.ts +1 -0
  193. package/corpus/templates/plan/server/lib/public-action-paths.ts +1 -0
  194. package/corpus/templates/plan/server/plan-content.ts +1 -1
  195. package/corpus/templates/plan/server/ui-plan-html.ts +2 -2
  196. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +6 -153
  197. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +53 -38
  198. package/corpus/templates/slides/app/components/design-system/{fig-import-response.ts → builder-index-response.ts} +9 -6
  199. package/corpus/templates/slides/app/components/visual-editor/MultiSelectChip.tsx +1 -1
  200. package/corpus/templates/slides/changelog/2026-06-29-design-system-setup-now-indexes-uploaded-design-files-throug.md +6 -0
  201. package/corpus/templates/slides/changelog/2026-06-29-longer-hosted-agent-turns.md +6 -0
  202. package/corpus/templates/slides/netlify.toml +3 -6
  203. package/corpus/templates/slides/server/handlers/index-design-system-with-builder.ts +116 -0
  204. package/corpus/templates/slides/server/lib/builder-design-system-proxy.ts +179 -0
  205. package/corpus/templates/slides/server/plugins/agent-chat.ts +4 -1
  206. package/corpus/templates/slides/server/routes/api/index-design-system-with-builder.post.ts +1 -0
  207. package/dist/action.d.ts +23 -0
  208. package/dist/action.d.ts.map +1 -1
  209. package/dist/action.js +9 -0
  210. package/dist/action.js.map +1 -1
  211. package/dist/agent/durable-background.d.ts +9 -7
  212. package/dist/agent/durable-background.d.ts.map +1 -1
  213. package/dist/agent/durable-background.js +27 -11
  214. package/dist/agent/durable-background.js.map +1 -1
  215. package/dist/agent/production-agent.d.ts +10 -0
  216. package/dist/agent/production-agent.d.ts.map +1 -1
  217. package/dist/agent/production-agent.js +4 -1
  218. package/dist/agent/production-agent.js.map +1 -1
  219. package/dist/agent/run-loop-with-resume.d.ts +2 -1
  220. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  221. package/dist/agent/run-loop-with-resume.js +2 -2
  222. package/dist/agent/run-loop-with-resume.js.map +1 -1
  223. package/dist/brand-kit/fig/index.d.ts +4 -4
  224. package/dist/brand-kit/fig/index.d.ts.map +1 -1
  225. package/dist/brand-kit/fig/index.js +14 -27
  226. package/dist/brand-kit/fig/index.js.map +1 -1
  227. package/dist/cli/index.js +1 -1
  228. package/dist/cli/index.js.map +1 -1
  229. package/dist/cli/mcp-config-writers.d.ts +15 -7
  230. package/dist/cli/mcp-config-writers.d.ts.map +1 -1
  231. package/dist/cli/mcp-config-writers.js +198 -45
  232. package/dist/cli/mcp-config-writers.js.map +1 -1
  233. package/dist/cli/plan-local.d.ts +30 -0
  234. package/dist/cli/plan-local.d.ts.map +1 -1
  235. package/dist/cli/plan-local.js +281 -56
  236. package/dist/cli/plan-local.js.map +1 -1
  237. package/dist/cli/skills.d.ts +2 -2
  238. package/dist/cli/skills.d.ts.map +1 -1
  239. package/dist/cli/skills.js +32 -8
  240. package/dist/cli/skills.js.map +1 -1
  241. package/dist/client/AssistantChat.d.ts.map +1 -1
  242. package/dist/client/AssistantChat.js +11 -1
  243. package/dist/client/AssistantChat.js.map +1 -1
  244. package/dist/client/agent-chat.d.ts.map +1 -1
  245. package/dist/client/agent-chat.js +15 -2
  246. package/dist/client/agent-chat.js.map +1 -1
  247. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  248. package/dist/client/composer/PromptComposer.js +35 -1
  249. package/dist/client/composer/PromptComposer.js.map +1 -1
  250. package/dist/client/org/OrgSwitcher.js +1 -1
  251. package/dist/client/org/OrgSwitcher.js.map +1 -1
  252. package/dist/client/sharing/ShareButton.d.ts +1 -1
  253. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  254. package/dist/client/sharing/ShareButton.js +2 -15
  255. package/dist/client/sharing/ShareButton.js.map +1 -1
  256. package/dist/collab/presence.d.ts +1 -1
  257. package/dist/collab/presence.js +1 -1
  258. package/dist/collab/presence.js.map +1 -1
  259. package/dist/collab/routes.d.ts +1 -1
  260. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  261. package/dist/mcp/connect-route.d.ts.map +1 -1
  262. package/dist/mcp/connect-route.js +34 -5
  263. package/dist/mcp/connect-route.js.map +1 -1
  264. package/dist/observability/routes.d.ts +2 -2
  265. package/dist/progress/routes.d.ts +1 -1
  266. package/dist/resources/handlers.d.ts +1 -1
  267. package/dist/server/action-discovery.d.ts.map +1 -1
  268. package/dist/server/action-discovery.js +5 -0
  269. package/dist/server/action-discovery.js.map +1 -1
  270. package/dist/server/action-routes.d.ts.map +1 -1
  271. package/dist/server/action-routes.js +14 -0
  272. package/dist/server/action-routes.js.map +1 -1
  273. package/dist/server/agent-chat-plugin.d.ts +6 -0
  274. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  275. package/dist/server/agent-chat-plugin.js +12 -3
  276. package/dist/server/agent-chat-plugin.js.map +1 -1
  277. package/dist/vite/client.d.ts.map +1 -1
  278. package/dist/vite/client.js +12 -1
  279. package/dist/vite/client.js.map +1 -1
  280. package/docs/content/locales/ar-SA/real-time-collaboration.mdx +2 -2
  281. package/docs/content/locales/ar-SA/template-design.mdx +1 -1
  282. package/docs/content/locales/de-DE/real-time-collaboration.mdx +3 -3
  283. package/docs/content/locales/de-DE/template-design.mdx +1 -1
  284. package/docs/content/locales/es-ES/real-time-collaboration.mdx +2 -2
  285. package/docs/content/locales/es-ES/template-design.mdx +1 -1
  286. package/docs/content/locales/fr-FR/real-time-collaboration.mdx +2 -2
  287. package/docs/content/locales/fr-FR/template-design.mdx +1 -1
  288. package/docs/content/locales/hi-IN/template-design.mdx +1 -1
  289. package/docs/content/locales/ja-JP/real-time-collaboration.mdx +2 -2
  290. package/docs/content/locales/ja-JP/template-design.mdx +1 -1
  291. package/docs/content/locales/ko-KR/real-time-collaboration.mdx +2 -2
  292. package/docs/content/locales/ko-KR/template-design.mdx +1 -1
  293. package/docs/content/locales/pt-BR/real-time-collaboration.mdx +2 -2
  294. package/docs/content/locales/pt-BR/template-design.mdx +1 -1
  295. package/docs/content/locales/zh-CN/real-time-collaboration.mdx +2 -2
  296. package/docs/content/locales/zh-CN/template-design.mdx +1 -1
  297. package/docs/content/locales/zh-TW/real-time-collaboration.mdx +2 -2
  298. package/docs/content/locales/zh-TW/template-design.mdx +1 -1
  299. package/docs/content/real-time-collaboration.mdx +2 -2
  300. package/docs/content/template-design.mdx +1 -1
  301. package/package.json +3 -7
  302. package/corpus/core/src/brand-kit/fig/decode.ts +0 -410
  303. package/corpus/core/src/brand-kit/fig/extract-design-system.ts +0 -947
  304. package/corpus/core/src/brand-kit/fig/fig-to-html.ts +0 -1906
  305. package/corpus/templates/design/actions/import-figma.ts +0 -55
  306. package/corpus/templates/design/changelog/2026-06-28-figma-style-code-design-mode.md +0 -6
  307. package/corpus/templates/design/server/handlers/import-figma-system.ts +0 -86
  308. package/corpus/templates/design/server/routes/api/import-figma-system.post.ts +0 -1
  309. package/corpus/templates/slides/server/handlers/import-figma-system.ts +0 -101
  310. package/corpus/templates/slides/server/routes/api/import-figma-system.post.ts +0 -1
  311. package/dist/brand-kit/fig/decode.d.ts +0 -33
  312. package/dist/brand-kit/fig/decode.d.ts.map +0 -1
  313. package/dist/brand-kit/fig/decode.js +0 -358
  314. package/dist/brand-kit/fig/decode.js.map +0 -1
  315. package/dist/brand-kit/fig/extract-design-system.d.ts +0 -44
  316. package/dist/brand-kit/fig/extract-design-system.d.ts.map +0 -1
  317. package/dist/brand-kit/fig/extract-design-system.js +0 -752
  318. package/dist/brand-kit/fig/extract-design-system.js.map +0 -1
  319. package/dist/brand-kit/fig/fig-to-html.d.ts +0 -246
  320. package/dist/brand-kit/fig/fig-to-html.d.ts.map +0 -1
  321. package/dist/brand-kit/fig/fig-to-html.js +0 -1506
  322. package/dist/brand-kit/fig/fig-to-html.js.map +0 -1
@@ -902,7 +902,11 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
902
902
  var key = e.key;
903
903
  var normalized = key && key.length === 1 ? key.toLowerCase() : key;
904
904
  var primary = e.metaKey || e.ctrlKey;
905
- if (key === 'Escape' || key === 'Enter' || key === 'Tab') return true;
905
+ if (key === 'Escape' || key === 'Enter') return true;
906
+ // Forward Tab only when an element is actively selected so the iframe does
907
+ // not intercept Tab when the user is tabbing through browser UI with nothing
908
+ // selected (preserves native keyboard accessibility).
909
+ if (key === 'Tab') return !!selectedEl;
906
910
  if (key === 'Delete' || key === 'Backspace') return !primary;
907
911
  if (/^Arrow/.test(key || '')) return !e.altKey;
908
912
  if (primary) {
@@ -955,7 +959,11 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
955
959
 
956
960
  function isInlineEditableDescendant(el) {
957
961
  if (!el || !el.tagName) return false;
962
+ // Allowlist covers inline markup AND common block-level text containers
963
+ // (p, h1-h6, li, etc.) so that paragraphs with inline markup like
964
+ // <p>Hello <strong>world</strong></p> can be double-click edited.
958
965
  return [
966
+ // Inline formatting
959
967
  'a',
960
968
  'abbr',
961
969
  'b',
@@ -972,7 +980,25 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
972
980
  'sup',
973
981
  'time',
974
982
  'u',
975
- 'wbr'
983
+ 'wbr',
984
+ // Block-level text containers
985
+ 'p',
986
+ 'h1',
987
+ 'h2',
988
+ 'h3',
989
+ 'h4',
990
+ 'h5',
991
+ 'h6',
992
+ 'li',
993
+ 'ul',
994
+ 'ol',
995
+ 'dl',
996
+ 'dt',
997
+ 'dd',
998
+ 'label',
999
+ 'caption',
1000
+ 'td',
1001
+ 'th'
976
1002
  ].indexOf(el.tagName.toLowerCase()) !== -1;
977
1003
  }
978
1004
 
@@ -999,14 +1025,27 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
999
1025
  if (selectedEl && node === selectedEl) break;
1000
1026
  node = node.parentElement;
1001
1027
  }
1002
- return candidate || hit;
1028
+ // Return null (not the raw hit) when no text-editable ancestor is found.
1029
+ // Falling back to the raw hit element makes non-text nodes like <img> or
1030
+ // <canvas> contenteditable, which leaves the editor in a broken state.
1031
+ return candidate || null;
1003
1032
  }
1004
1033
 
1005
1034
  function selectElementAtEvent(e) {
1006
1035
  stopNativeInteraction(e);
1007
1036
  blurActiveTextEditor();
1037
+ // Suppress the first click in a double-click sequence — the dblclick
1038
+ // handler (beginTextEditingFromEvent) will fire immediately after and a
1039
+ // spurious element-select would cause inspector flicker.
1040
+ if (e.detail >= 2) return;
1008
1041
  var target = elementFromEditorPoint(e.clientX, e.clientY);
1009
- if (!target) return;
1042
+ if (!target || target === document.body || target === document.documentElement) {
1043
+ // Click on empty canvas: clear the current selection (matches Figma).
1044
+ selectedEl = null;
1045
+ selectionOverlay.style.display = 'none';
1046
+ window.parent.postMessage({ type: 'clear-selection' }, '*');
1047
+ return;
1048
+ }
1010
1049
  selectedEl = target;
1011
1050
  var info = getElementInfo(selectedEl);
1012
1051
  positionOverlay(selectionOverlay, selectedEl);
@@ -1225,7 +1264,12 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
1225
1264
  function parentFlowAxis(parent) {
1226
1265
  var cs = window.getComputedStyle(parent);
1227
1266
  if (cs.display === 'flex' || cs.display === 'inline-flex') {
1228
- return cs.flexDirection && cs.flexDirection.indexOf('row') === 0 ? 'x' : 'y';
1267
+ var isRow = cs.flexDirection && cs.flexDirection.indexOf('row') === 0;
1268
+ // Wrapping row containers need Y-axis awareness for inter-row targeting;
1269
+ // fall back to column-axis insertion so the heuristic picks the right row.
1270
+ var wraps = cs.flexWrap === 'wrap' || cs.flexWrap === 'wrap-reverse';
1271
+ if (isRow && !wraps) return 'x';
1272
+ return 'y';
1229
1273
  }
1230
1274
  if (cs.display === 'grid' || cs.display === 'inline-grid') {
1231
1275
  var cols = (cs.gridTemplateColumns || '').split(' ').filter(Boolean).length;
@@ -1243,6 +1287,7 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
1243
1287
  hit !== document.documentElement &&
1244
1288
  hit !== el &&
1245
1289
  !el.contains(hit) &&
1290
+ !hit.contains(el) &&
1246
1291
  !isOverlayElement(hit)
1247
1292
  ) {
1248
1293
  var hitChildren = draggableElementChildren(hit).filter(function(child) {
@@ -1383,10 +1428,13 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
1383
1428
  window.parent.postMessage({ type: 'element-select', payload: getElementInfo(selectedEl) }, '*');
1384
1429
  }
1385
1430
  if (isFlowReorderCandidate(selectedEl)) {
1386
- var currentTarget = reorderTargetForPoint(selectedEl, e.clientX, e.clientY);
1431
+ // Snapshot the element being reordered so a concurrent select-element or
1432
+ // clear-selection postMessage cannot mutate the wrong element mid-drag.
1433
+ var reorderEl = selectedEl;
1434
+ var currentTarget = reorderTargetForPoint(reorderEl, e.clientX, e.clientY);
1387
1435
  showInsertionGuideFor(currentTarget);
1388
1436
  function onReorderMove(ev) {
1389
- currentTarget = reorderTargetForPoint(selectedEl, ev.clientX, ev.clientY);
1437
+ currentTarget = reorderTargetForPoint(reorderEl, ev.clientX, ev.clientY);
1390
1438
  showInsertionGuideFor(currentTarget);
1391
1439
  showTransformBadge(currentTarget ? 'Move layer' : 'Move', ev.clientX, ev.clientY);
1392
1440
  }
@@ -1395,12 +1443,27 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
1395
1443
  document.removeEventListener('mouseup', onReorderUp, true);
1396
1444
  hideTransformBadge();
1397
1445
  hideInsertionGuide();
1398
- if (!currentTarget) return;
1446
+ if (!currentTarget) {
1447
+ // No valid drop target — clean up the clone if one was inserted so
1448
+ // no ghost element is left in the DOM.
1449
+ if (duplicatedForDrag && reorderEl && reorderEl !== originalSelectedEl) {
1450
+ if (reorderEl.parentElement) reorderEl.parentElement.removeChild(reorderEl);
1451
+ selectedEl = originalSelectedEl;
1452
+ positionOverlay(selectionOverlay, selectedEl);
1453
+ window.parent.postMessage({ type: 'element-select', payload: getElementInfo(selectedEl) }, '*');
1454
+ }
1455
+ return;
1456
+ }
1399
1457
  if (duplicatedForDrag) {
1400
- applyRuntimeReorder(selectedEl, currentTarget);
1401
- postVisualDuplicateChange(originalSelectedEl, selectedEl, currentTarget);
1458
+ applyRuntimeReorder(reorderEl, currentTarget);
1459
+ postVisualDuplicateChange(originalSelectedEl, reorderEl, currentTarget);
1460
+ } else {
1461
+ // Optimistically apply the reorder in the DOM for immediate
1462
+ // visual feedback; the visual-structure-ack handler will confirm
1463
+ // or revert once the parent processes the change.
1464
+ applyRuntimeReorder(reorderEl, currentTarget);
1465
+ postVisualStructureChange(reorderEl, currentTarget);
1402
1466
  }
1403
- else postVisualStructureChange(selectedEl, currentTarget);
1404
1467
  }
1405
1468
  document.addEventListener('mousemove', onReorderMove, true);
1406
1469
  document.addEventListener('mouseup', onReorderUp, true);
@@ -1412,11 +1475,15 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
1412
1475
  var originTop = readPx(selectedEl.style.top || cs.top);
1413
1476
  var startX = e.clientX;
1414
1477
  var startY = e.clientY;
1478
+ // Snapshot the element being moved so that a concurrent select-element or
1479
+ // clear-selection postMessage cannot swap selectedEl mid-drag and cause
1480
+ // mutations on the wrong element or a null-deref in onUp.
1481
+ var dragEl = selectedEl;
1415
1482
  function onMove(ev) {
1416
1483
  var nextLeft = originLeft + ev.clientX - startX;
1417
1484
  var nextTop = originTop + ev.clientY - startY;
1418
- selectedEl.style.left = Math.round(nextLeft) + 'px';
1419
- selectedEl.style.top = Math.round(nextTop) + 'px';
1485
+ dragEl.style.left = Math.round(nextLeft) + 'px';
1486
+ dragEl.style.top = Math.round(nextTop) + 'px';
1420
1487
  showTransformBadge('X ' + Math.round(nextLeft) + ' Y ' + Math.round(nextTop), ev.clientX, ev.clientY);
1421
1488
  refreshOverlays();
1422
1489
  }
@@ -1424,14 +1491,20 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
1424
1491
  document.removeEventListener('mousemove', onMove, true);
1425
1492
  document.removeEventListener('mouseup', onUp, true);
1426
1493
  hideTransformBadge();
1494
+ if (!dragEl) return;
1427
1495
  if (duplicatedForDrag) {
1428
- postVisualDuplicateChange(originalSelectedEl, selectedEl);
1496
+ postVisualDuplicateChange(originalSelectedEl, dragEl);
1429
1497
  } else {
1430
- postVisualStyleChange({
1431
- position: selectedEl.style.position,
1432
- left: selectedEl.style.left,
1433
- top: selectedEl.style.top,
1434
- });
1498
+ window.parent.postMessage({
1499
+ type: 'visual-style-change',
1500
+ selector: getSelector(dragEl),
1501
+ styles: {
1502
+ position: dragEl.style.position,
1503
+ left: dragEl.style.left,
1504
+ top: dragEl.style.top,
1505
+ },
1506
+ payload: getElementInfo(dragEl),
1507
+ }, '*');
1435
1508
  }
1436
1509
  }
1437
1510
  document.addEventListener('mousemove', onMove, true);
@@ -1445,15 +1518,23 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
1445
1518
  e.stopPropagation();
1446
1519
  ensurePositionable(selectedEl);
1447
1520
  var cs = window.getComputedStyle(selectedEl);
1521
+ // Bug fix: use CSS width/height (not getBoundingClientRect) for the resize
1522
+ // origin dimensions so that rotated elements don't use the inflated
1523
+ // axis-aligned bounding box as the starting size.
1524
+ var originW = readPx(selectedEl.style.width || cs.width);
1525
+ var originH = readPx(selectedEl.style.height || cs.height);
1448
1526
  var origin = {
1449
1527
  left: readPx(selectedEl.style.left || cs.left),
1450
1528
  top: readPx(selectedEl.style.top || cs.top),
1451
- width: selectedEl.getBoundingClientRect().width,
1452
- height: selectedEl.getBoundingClientRect().height,
1453
- ratio: selectedEl.getBoundingClientRect().width / Math.max(1, selectedEl.getBoundingClientRect().height),
1529
+ width: originW,
1530
+ height: originH,
1531
+ ratio: originW / Math.max(1, originH),
1454
1532
  };
1455
1533
  var startX = e.clientX;
1456
1534
  var startY = e.clientY;
1535
+ // Snapshot the element so a concurrent clear-selection postMessage cannot
1536
+ // cause a null-deref in onMove/onUp.
1537
+ var resizeEl = selectedEl;
1457
1538
  function nextRect(ev) {
1458
1539
  var dx = ev.clientX - startX;
1459
1540
  var dy = ev.clientY - startY;
@@ -1471,12 +1552,32 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
1471
1552
  height = origin.height - dy;
1472
1553
  }
1473
1554
  if (handle.indexOf('s') !== -1) height = origin.height + dy;
1474
- width = Math.max(8, width);
1475
- height = Math.max(8, height);
1476
- if ((ev.shiftKey || scaleToolEnabled) && handle.length === 2) {
1555
+ // Apply Shift / scaleToolEnabled aspect-ratio lock BEFORE the min-size
1556
+ // clamp so the ratio is computed from unclamped values (bug fix).
1557
+ if (ev.shiftKey && handle.length === 2) {
1477
1558
  if (Math.abs(dx) > Math.abs(dy)) height = width / origin.ratio;
1478
1559
  else width = height * origin.ratio;
1479
1560
  }
1561
+ if (scaleToolEnabled) {
1562
+ // Scale tool: enforce aspect ratio on all 8 handles, not just corners.
1563
+ if (handle === 'e' || handle === 'w') {
1564
+ height = width / origin.ratio;
1565
+ } else if (handle === 'n' || handle === 's') {
1566
+ width = height * origin.ratio;
1567
+ } else if (handle.length === 2) {
1568
+ if (Math.abs(dx) > Math.abs(dy)) height = width / origin.ratio;
1569
+ else width = height * origin.ratio;
1570
+ }
1571
+ }
1572
+ // Clamp after ratio correction.
1573
+ var rawWidth = width;
1574
+ var rawHeight = height;
1575
+ width = Math.max(8, width);
1576
+ height = Math.max(8, height);
1577
+ // Re-anchor the pinned edge for w/n handles after clamping so the
1578
+ // opposite edge doesn't drift when the element hits minimum size.
1579
+ if (handle.indexOf('w') !== -1) left += (rawWidth - width);
1580
+ if (handle.indexOf('n') !== -1) top += (rawHeight - height);
1480
1581
  if (ev.altKey) {
1481
1582
  if (handle.indexOf('w') !== -1 || handle.indexOf('e') !== -1) left = origin.left - (width - origin.width) / 2;
1482
1583
  if (handle.indexOf('n') !== -1 || handle.indexOf('s') !== -1) top = origin.top - (height - origin.height) / 2;
@@ -1484,11 +1585,12 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
1484
1585
  return { left: left, top: top, width: width, height: height };
1485
1586
  }
1486
1587
  function onMove(ev) {
1588
+ if (!resizeEl) return;
1487
1589
  var rect = nextRect(ev);
1488
- selectedEl.style.left = Math.round(rect.left) + 'px';
1489
- selectedEl.style.top = Math.round(rect.top) + 'px';
1490
- selectedEl.style.width = Math.round(rect.width) + 'px';
1491
- selectedEl.style.height = Math.round(rect.height) + 'px';
1590
+ resizeEl.style.left = Math.round(rect.left) + 'px';
1591
+ resizeEl.style.top = Math.round(rect.top) + 'px';
1592
+ resizeEl.style.width = Math.round(rect.width) + 'px';
1593
+ resizeEl.style.height = Math.round(rect.height) + 'px';
1492
1594
  showTransformBadge(Math.round(rect.width) + ' x ' + Math.round(rect.height), ev.clientX, ev.clientY);
1493
1595
  refreshOverlays();
1494
1596
  }
@@ -1496,13 +1598,19 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
1496
1598
  document.removeEventListener('mousemove', onMove, true);
1497
1599
  document.removeEventListener('mouseup', onUp, true);
1498
1600
  hideTransformBadge();
1499
- postVisualStyleChange({
1500
- position: selectedEl.style.position,
1501
- left: selectedEl.style.left,
1502
- top: selectedEl.style.top,
1503
- width: selectedEl.style.width,
1504
- height: selectedEl.style.height,
1505
- });
1601
+ if (!resizeEl) return;
1602
+ window.parent.postMessage({
1603
+ type: 'visual-style-change',
1604
+ selector: getSelector(resizeEl),
1605
+ styles: {
1606
+ position: resizeEl.style.position,
1607
+ left: resizeEl.style.left,
1608
+ top: resizeEl.style.top,
1609
+ width: resizeEl.style.width,
1610
+ height: resizeEl.style.height,
1611
+ },
1612
+ payload: getElementInfo(resizeEl),
1613
+ }, '*');
1506
1614
  }
1507
1615
  document.addEventListener('mousemove', onMove, true);
1508
1616
  document.addEventListener('mouseup', onUp, true);
@@ -1513,16 +1621,22 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
1513
1621
  if (isLayerInteractionBlocked(selectedEl)) return;
1514
1622
  e.preventDefault();
1515
1623
  e.stopPropagation();
1624
+ // getBoundingClientRect is correct here — we only need the element center
1625
+ // for angle math, and the element's visual position is what we want.
1516
1626
  var rect = selectedEl.getBoundingClientRect();
1517
1627
  var center = { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
1518
1628
  var originAngle = Math.atan2(e.clientY - center.y, e.clientX - center.x) * 180 / Math.PI;
1519
1629
  var originRotation = currentRotation(selectedEl);
1630
+ // Snapshot so a concurrent clear-selection postMessage cannot cause a
1631
+ // null-deref in onMove/onUp.
1632
+ var rotateEl = selectedEl;
1520
1633
  function onMove(ev) {
1634
+ if (!rotateEl) return;
1521
1635
  var pointerAngle = Math.atan2(ev.clientY - center.y, ev.clientX - center.x) * 180 / Math.PI;
1522
1636
  var next = originRotation + pointerAngle - originAngle;
1523
1637
  if (ev.shiftKey) next = Math.round(next / 15) * 15;
1524
1638
  next = Math.round(next);
1525
- selectedEl.style.transform = mergeRotation(selectedEl, next);
1639
+ rotateEl.style.transform = mergeRotation(rotateEl, next);
1526
1640
  showTransformBadge(next + 'deg', ev.clientX, ev.clientY);
1527
1641
  refreshOverlays();
1528
1642
  }
@@ -1530,9 +1644,13 @@ const EDITOR_CHROME_BRIDGE_SCRIPT = `
1530
1644
  document.removeEventListener('mousemove', onMove, true);
1531
1645
  document.removeEventListener('mouseup', onUp, true);
1532
1646
  hideTransformBadge();
1533
- postVisualStyleChange({
1534
- transform: selectedEl.style.transform,
1535
- });
1647
+ if (!rotateEl) return;
1648
+ window.parent.postMessage({
1649
+ type: 'visual-style-change',
1650
+ selector: getSelector(rotateEl),
1651
+ styles: { transform: rotateEl.style.transform },
1652
+ payload: getElementInfo(rotateEl),
1653
+ }, '*');
1536
1654
  }
1537
1655
  document.addEventListener('mousemove', onMove, true);
1538
1656
  document.addEventListener('mouseup', onUp, true);
@@ -2531,7 +2649,7 @@ export function DesignCanvas({
2531
2649
  sandbox={
2532
2650
  externalPreviewUrl
2533
2651
  ? "allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin"
2534
- : "allow-scripts allow-popups allow-popups-to-escape-sandbox"
2652
+ : "allow-scripts allow-popups allow-popups-to-escape-sandbox allow-same-origin"
2535
2653
  }
2536
2654
  data-design-preview-iframe
2537
2655
  className="block h-full w-full border-0 bg-transparent"
@@ -16,6 +16,7 @@ import {
16
16
  } from "@tabler/icons-react";
17
17
  import { useQuery, useQueryClient } from "@tanstack/react-query";
18
18
  import { useMemo, useState } from "react";
19
+ import { toast } from "sonner";
19
20
 
20
21
  import { Button } from "@/components/ui/button";
21
22
  import {
@@ -207,7 +208,7 @@ export function DesignExtensionsPanel({
207
208
  const installExtension = async (extensionId: string) => {
208
209
  setInstallingId(extensionId);
209
210
  try {
210
- await fetch(
211
+ const res = await fetch(
211
212
  agentNativePath(
212
213
  `/_agent-native/slots/${encodeURIComponent(slotId)}/install`,
213
214
  ),
@@ -217,20 +218,23 @@ export function DesignExtensionsPanel({
217
218
  body: JSON.stringify({ extensionId }),
218
219
  },
219
220
  );
220
- } finally {
221
- setInstallingId(null);
221
+ if (!res.ok) throw new Error(`Install failed: ${res.status}`);
222
222
  queryClient.invalidateQueries({
223
223
  queryKey: ["design-editor-extension-slot"],
224
224
  });
225
225
  queryClient.invalidateQueries({
226
226
  queryKey: ["design-editor-extension-slot-available"],
227
227
  });
228
+ } catch {
229
+ toast.error(t("designEditor.extensionsInstallError"));
230
+ } finally {
231
+ setInstallingId(null);
228
232
  }
229
233
  };
230
234
 
231
235
  return (
232
236
  <div className={cn("flex min-h-0 flex-1 flex-col", className)}>
233
- {/* Section header — 32 px tall, matches PanelSection / Figma inspector headers */}
237
+ {/* Section header — 32 px tall, matches PanelSection / design inspector headers */}
234
238
  <div className="flex min-h-8 shrink-0 items-center gap-1.5 border-b border-border/60 px-3">
235
239
  <h3 className="min-w-0 flex-1 truncate text-xs font-semibold text-foreground">
236
240
  {t("designEditor.extensions")}
@@ -172,56 +172,51 @@ export function DrawOverlay({ visible, onQueue, onSend }: DrawOverlayProps) {
172
172
  const queueDrawing = () => {
173
173
  if (strokes.length === 0) return;
174
174
 
175
- // Convert strokes to SVG path data
176
- const pathData = strokes
177
- .map((s) => {
178
- const d = s.points
179
- .map(
180
- (p, i) =>
181
- `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`,
182
- )
183
- .join(" ");
184
- return d;
185
- })
186
- .join(" ");
175
+ // Convert each stroke into its own annotation to preserve per-stroke color/lineWidth
176
+ const newAnnotations: DrawAnnotation[] = strokes.map((s) => {
177
+ const pathData = s.points
178
+ .map(
179
+ (p, i) => `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`,
180
+ )
181
+ .join(" ");
182
+ return {
183
+ id: crypto.randomUUID(),
184
+ type: "path",
185
+ pathData,
186
+ position: { x: 0, y: 0 },
187
+ color: s.color,
188
+ lineWidth: s.lineWidth,
189
+ };
190
+ });
187
191
 
188
- const annotation: DrawAnnotation = {
189
- id: crypto.randomUUID(),
190
- type: "path",
191
- pathData,
192
- position: { x: 0, y: 0 },
193
- color,
194
- lineWidth,
195
- };
196
- onQueue(annotation);
197
- setQueued((prev) => [...prev, annotation]);
192
+ for (const annotation of newAnnotations) {
193
+ onQueue(annotation);
194
+ }
195
+ setQueued((prev) => [...prev, ...newAnnotations]);
198
196
  setStrokes([]);
199
197
  };
200
198
 
201
199
  const sendAll = () => {
202
- // Queue current drawing first if any
200
+ // Queue current drawing first if any; preserve per-stroke color/lineWidth
203
201
  let allAnnotations = [...queued];
204
202
  if (strokes.length > 0) {
205
- const pathData = strokes
206
- .map((s) => {
207
- const d = s.points
208
- .map(
209
- (p, i) =>
210
- `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`,
211
- )
212
- .join(" ");
213
- return d;
214
- })
215
- .join(" ");
216
-
217
- allAnnotations.push({
218
- id: crypto.randomUUID(),
219
- type: "path",
220
- pathData,
221
- position: { x: 0, y: 0 },
222
- color,
223
- lineWidth,
203
+ const strokeAnnotations: DrawAnnotation[] = strokes.map((s) => {
204
+ const pathData = s.points
205
+ .map(
206
+ (p, i) =>
207
+ `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`,
208
+ )
209
+ .join(" ");
210
+ return {
211
+ id: crypto.randomUUID(),
212
+ type: "path",
213
+ pathData,
214
+ position: { x: 0, y: 0 },
215
+ color: s.color,
216
+ lineWidth: s.lineWidth,
217
+ };
224
218
  });
219
+ allAnnotations = [...allAnnotations, ...strokeAnnotations];
225
220
  }
226
221
 
227
222
  if (allAnnotations.length > 0) {