@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
@@ -52,10 +52,10 @@ import { ShaderFillsPanel, shaderDescriptorToCss } from "./ShaderFillsPanel";
52
52
 
53
53
  // ─── Public types ──────────────────────────────────────────────────────────────
54
54
 
55
- export type FigmaColorMode = "hex" | "rgb" | "hsl" | "hsb";
56
- export type FigmaGradientType = "linear" | "radial" | "angular" | "diamond";
57
- export type FigmaFillType = "solid" | "gradient" | "image";
58
- export type FigmaPaintType =
55
+ export type DesignColorMode = "hex" | "rgb" | "hsl" | "hsb";
56
+ export type DesignGradientType = "linear" | "radial" | "angular" | "diamond";
57
+ export type DesignFillType = "solid" | "gradient" | "image";
58
+ export type DesignPaintType =
59
59
  | "solid"
60
60
  | "linear"
61
61
  | "radial"
@@ -70,22 +70,22 @@ export type FigmaPaintType =
70
70
 
71
71
  // These interfaces remain so EditPanel's prop types don't break, even though
72
72
  // the popover no longer renders the fills/gradient-stops list.
73
- export interface FigmaFillRow {
73
+ export interface DesignFillRow {
74
74
  id: string;
75
75
  label: string;
76
76
  value: string;
77
- type: FigmaFillType;
77
+ type: DesignFillType;
78
78
  opacity?: number;
79
79
  swatch?: string;
80
80
  selected?: boolean;
81
81
  }
82
82
 
83
- export interface FigmaFillRowPatch {
83
+ export interface DesignFillRowPatch {
84
84
  value?: string;
85
85
  opacity?: number;
86
86
  }
87
87
 
88
- export interface FigmaGradientStop {
88
+ export interface DesignGradientStop {
89
89
  id: string;
90
90
  color: string;
91
91
  position: number;
@@ -93,13 +93,13 @@ export interface FigmaGradientStop {
93
93
  label?: string;
94
94
  }
95
95
 
96
- export interface FigmaGradientStopPatch {
96
+ export interface DesignGradientStopPatch {
97
97
  color?: string;
98
98
  position?: number;
99
99
  opacity?: number;
100
100
  }
101
101
 
102
- export interface FigmaColorPickerLabels {
102
+ export interface DesignColorPickerLabels {
103
103
  trigger: string;
104
104
  hex: string;
105
105
  red: string;
@@ -126,7 +126,7 @@ export interface FigmaColorPickerLabels {
126
126
  diamond: string;
127
127
  }
128
128
 
129
- export interface FigmaColorPickerProps {
129
+ export interface DesignColorPickerProps {
130
130
  value: string;
131
131
  onChange: (value: string) => void;
132
132
  onPaintValueChange?: (value: string) => void;
@@ -137,21 +137,21 @@ export interface FigmaColorPickerProps {
137
137
  onBlendModeChange?: (mode: string) => void;
138
138
  showBlendMode?: boolean;
139
139
  // Accepted but unused in the popover — list management lives in the sidebar.
140
- fillRows?: FigmaFillRow[];
140
+ fillRows?: DesignFillRow[];
141
141
  selectedFillId?: string;
142
142
  onFillSelect?: (id: string) => void;
143
- onFillChange?: (id: string, patch: FigmaFillRowPatch) => void;
143
+ onFillChange?: (id: string, patch: DesignFillRowPatch) => void;
144
144
  onAddFill?: () => void;
145
145
  onRemoveFill?: (id: string) => void;
146
- paintType?: FigmaPaintType;
147
- onPaintTypeChange?: (type: FigmaPaintType) => void;
148
- gradientType?: FigmaGradientType;
149
- onGradientTypeChange?: (type: FigmaGradientType) => void;
146
+ paintType?: DesignPaintType;
147
+ onPaintTypeChange?: (type: DesignPaintType) => void;
148
+ gradientType?: DesignGradientType;
149
+ onGradientTypeChange?: (type: DesignGradientType) => void;
150
150
  // Accepted but unused in the popover — gradient stop handles belong on canvas.
151
- gradientStops?: FigmaGradientStop[];
151
+ gradientStops?: DesignGradientStop[];
152
152
  selectedStopId?: string;
153
153
  onGradientStopSelect?: (id: string) => void;
154
- onGradientStopChange?: (id: string, patch: FigmaGradientStopPatch) => void;
154
+ onGradientStopChange?: (id: string, patch: DesignGradientStopPatch) => void;
155
155
  onAddGradientStop?: () => void;
156
156
  onRemoveGradientStop?: (id: string) => void;
157
157
  /**
@@ -173,7 +173,7 @@ export interface FigmaColorPickerProps {
173
173
  };
174
174
  /** Notified when a shader fill is applied/tuned (descriptor + CSS fallback). */
175
175
  onShaderChange?: (descriptor: ShaderDescriptor, css: string) => void;
176
- labels?: Partial<FigmaColorPickerLabels>;
176
+ labels?: Partial<DesignColorPickerLabels>;
177
177
  disabled?: boolean;
178
178
  className?: string;
179
179
  }
@@ -191,7 +191,7 @@ interface HsvaColor {
191
191
 
192
192
  const FALLBACK_COLOR: RgbaColor = { r: 0, g: 0, b: 0, a: 1 };
193
193
 
194
- const DEFAULT_LABELS: FigmaColorPickerLabels = {
194
+ const DEFAULT_LABELS: DesignColorPickerLabels = {
195
195
  trigger: "Open color picker", // i18n-ignore fallback component label
196
196
  hex: "Hex", // i18n-ignore fallback component label
197
197
  red: "R", // i18n-ignore fallback component label
@@ -218,7 +218,7 @@ const DEFAULT_LABELS: FigmaColorPickerLabels = {
218
218
  diamond: "Diamond", // i18n-ignore fallback component label
219
219
  };
220
220
 
221
- // Figma-matching checkerboard: explicit light/dark tiles for legibility.
221
+ // checkerboard: explicit light/dark tiles for legibility.
222
222
  const CHECKER_A = "#d4d4d4";
223
223
  const CHECKER_B = "#a3a3a3";
224
224
  const CHECKERBOARD_IMAGE = `linear-gradient(45deg, ${CHECKER_A} 25%, transparent 25%), linear-gradient(-45deg, ${CHECKER_A} 25%, transparent 25%), linear-gradient(45deg, transparent 75%, ${CHECKER_A} 75%), linear-gradient(-45deg, transparent 75%, ${CHECKER_A} 75%)`;
@@ -448,7 +448,7 @@ function IconNoneFill({ className }: { className?: string }) {
448
448
  }
449
449
 
450
450
  function IconShaderFill({ className }: { className?: string }) {
451
- // Droplet — Figma uses a teardrop for shader/blur-type fills.
451
+ // Droplet — the design editor uses a teardrop for shader/blur-type fills.
452
452
  return (
453
453
  <svg
454
454
  viewBox="0 0 24 24"
@@ -534,7 +534,7 @@ function IconPatternFill({ className }: { className?: string }) {
534
534
  // ─── Paint type definitions (only supported types rendered) ────────────────────
535
535
 
536
536
  const PAINT_TYPES: Array<{
537
- type: FigmaPaintType;
537
+ type: DesignPaintType;
538
538
  label: string;
539
539
  Icon: (props: { className?: string }) => JSX.Element;
540
540
  }> = [
@@ -555,7 +555,7 @@ const PAINT_TYPES: Array<{
555
555
  // Both point at the same member set — keep reads using this name so the
556
556
  // component body compiles even though the exported constant is declared
557
557
  // at the bottom of the file (hoisting doesn't apply to const).
558
- const GRADIENT_TYPES = new Set<FigmaPaintType>([
558
+ const GRADIENT_TYPES = new Set<DesignPaintType>([
559
559
  "linear",
560
560
  "radial",
561
561
  "angular",
@@ -571,7 +571,7 @@ const PATTERN_FALLBACK_CSS =
571
571
 
572
572
  // ─── Main component ────────────────────────────────────────────────────────────
573
573
 
574
- export function FigmaColorPicker({
574
+ export function DesignColorPicker({
575
575
  value,
576
576
  onChange,
577
577
  onPaintValueChange,
@@ -603,24 +603,28 @@ export function FigmaColorPicker({
603
603
  labels,
604
604
  disabled = false,
605
605
  className,
606
- }: FigmaColorPickerProps) {
606
+ }: DesignColorPickerProps) {
607
607
  const copy = { ...DEFAULT_LABELS, ...labels };
608
608
  const color = parseCssColor(value) ?? FALLBACK_COLOR;
609
609
  const hsv = rgbaToHsv(color);
610
610
  const hsl = rgbaToHsl(color);
611
611
  const effectiveOpacity = opacity ?? alphaToOpacity(color.a);
612
612
 
613
- const [mode, setMode] = useState<FigmaColorMode>("hex");
613
+ const [mode, setMode] = useState<DesignColorMode>("hex");
614
614
  const [hexDraft, setHexDraft] = useState(() => toDisplayHex(color));
615
615
  const [open, setOpen] = useState(false);
616
616
  const [picking, setPicking] = useState(false);
617
617
  const skipNextHexBlurCommitRef = useRef(false);
618
+ // Preserve the last non-zero hue so dragging through an achromatic point
619
+ // (s=0 or v=0) doesn't snap hue to 0° when the user drags back to a
620
+ // saturated region. Matches Figma's hue-preservation behavior.
621
+ const lastHueRef = useRef<number>(0);
618
622
 
619
- // Whole-popover view: the standard picker, or the Figma "Shader fills" panel.
623
+ // Whole-popover view: the standard picker, or the shader fills panel.
620
624
  const [view, setView] = useState<"picker" | "shader">("picker");
621
625
 
622
626
  // Self-managed paint-type fallback for when EditPanel doesn't drive it.
623
- const [localPaintType, setLocalPaintType] = useState<FigmaPaintType | null>(
627
+ const [localPaintType, setLocalPaintType] = useState<DesignPaintType | null>(
624
628
  null,
625
629
  );
626
630
 
@@ -642,7 +646,7 @@ export function FigmaColorPicker({
642
646
  // engages its editor even when EditPanel doesn't complete the structural
643
647
  // fill switch. localPaintType is reset below when the prop changes (i.e. a
644
648
  // different element/fill is selected) so the picker still follows selection.
645
- const effectivePaintType: FigmaPaintType =
649
+ const effectivePaintType: DesignPaintType =
646
650
  localPaintType ?? paintType ?? inferPaintType(value, effectiveOpacity);
647
651
 
648
652
  // Resolve the active gradient: prefer EditPanel-driven props; otherwise parse
@@ -739,7 +743,7 @@ export function FigmaColorPicker({
739
743
  const emitGradient = (next: GradientValue) => {
740
744
  setLocalGradient(next);
741
745
  if (onGradientTypeChange && next.kind !== gradientType) {
742
- onGradientTypeChange(next.kind as FigmaGradientType);
746
+ onGradientTypeChange(next.kind as DesignGradientType);
743
747
  }
744
748
  emitPaintValue(gradientToCss(next));
745
749
  };
@@ -752,7 +756,15 @@ export function FigmaColorPicker({
752
756
  const fieldColor: RgbaColor = activeGradient
753
757
  ? (parseCssColor(selectedStop?.color ?? "#000000") ?? FALLBACK_COLOR)
754
758
  : color;
755
- const fieldHsv = rgbaToHsv(fieldColor);
759
+ const rawFieldHsv = rgbaToHsv(fieldColor);
760
+ // Preserve the last non-zero hue so dragging through gray doesn't lose it.
761
+ if (rawFieldHsv.s > 0 && rawFieldHsv.v > 0) {
762
+ lastHueRef.current = rawFieldHsv.h;
763
+ }
764
+ const fieldHsv: HsvaColor =
765
+ rawFieldHsv.s === 0
766
+ ? { ...rawFieldHsv, h: lastHueRef.current }
767
+ : rawFieldHsv;
756
768
  const fieldHsl = rgbaToHsl(fieldColor);
757
769
 
758
770
  // In gradient mode, mirror the selected stop's color into the hex draft.
@@ -808,10 +820,10 @@ export function FigmaColorPicker({
808
820
 
809
821
  // ── Paint-type switching (does real work for every type) ──────────────────────
810
822
 
811
- const setPaintType = (nextType: FigmaPaintType) => {
823
+ const setPaintType = (nextType: DesignPaintType) => {
812
824
  if (disabled) return;
813
825
 
814
- // Shader opens the dedicated Figma "Shader fills" panel.
826
+ // Shader opens the dedicated shader fills panel.
815
827
  if (nextType === "shader") {
816
828
  setLocalPaintType("shader");
817
829
  setView("shader");
@@ -1007,7 +1019,7 @@ export function FigmaColorPicker({
1007
1019
  <div className={cn("space-y-1.5", className)}>
1008
1020
  <Popover open={open} onOpenChange={setOpen}>
1009
1021
  <PopoverTrigger asChild>
1010
- {/* Trigger: compact swatch + hex + opacity% — matches Figma's fill row */}
1022
+ {/* Trigger: compact swatch + hex + opacity% — matches the design editor's fill row */}
1011
1023
  <button
1012
1024
  type="button"
1013
1025
  disabled={disabled}
@@ -1018,7 +1030,7 @@ export function FigmaColorPicker({
1018
1030
  disabled && "pointer-events-none opacity-50",
1019
1031
  )}
1020
1032
  >
1021
- {/* Flat swatch chip — no shadow-inner (Figma is flat) */}
1033
+ {/* Flat swatch chip — no shadow-inner (the design editor uses a flat chip) */}
1022
1034
  <span
1023
1035
  className="size-4 shrink-0 rounded-[3px] border border-border/60"
1024
1036
  style={triggerSwatchStyle(value, color)}
@@ -1032,7 +1044,7 @@ export function FigmaColorPicker({
1032
1044
  </button>
1033
1045
  </PopoverTrigger>
1034
1046
 
1035
- {/* Figma popover: ~240px wide, uniform 12px padding, tight controls */}
1047
+ {/* design popover: ~240px wide, uniform 12px padding, tight controls */}
1036
1048
  <PopoverContent
1037
1049
  side="left"
1038
1050
  align="start"
@@ -1056,7 +1068,7 @@ export function FigmaColorPicker({
1056
1068
  />
1057
1069
  ) : (
1058
1070
  <>
1059
- {/* ── Paint-type icon row (Figma-style, full-width tabs) ─── */}
1071
+ {/* ── Paint-type icon row (design-editor, full-width tabs) ─── */}
1060
1072
  {/* 11 types → two rows: first 6, then 5. Each icon is a
1061
1073
  clearly-hittable 36×32px target with a distinct active
1062
1074
  accent so the selected mode is immediately obvious. */}
@@ -1210,7 +1222,7 @@ export function FigmaColorPicker({
1210
1222
  emitStopColor(
1211
1223
  hsvToRgba({
1212
1224
  ...nextHsv,
1213
- a: opacityToAlpha(effectiveOpacity),
1225
+ a: fieldColor.a,
1214
1226
  }),
1215
1227
  );
1216
1228
  } else {
@@ -1286,7 +1298,7 @@ export function FigmaColorPicker({
1286
1298
  }}
1287
1299
  />
1288
1300
 
1289
- {/* Current-color swatch left of alpha (matches Figma's layout) */}
1301
+ {/* Current-color swatch left of alpha (matches the design editor's layout) */}
1290
1302
  <div className="flex items-center gap-2">
1291
1303
  <span
1292
1304
  className="size-[18px] shrink-0 rounded-[3px] border border-border/60"
@@ -1330,11 +1342,11 @@ export function FigmaColorPicker({
1330
1342
  activeGradient) && (
1331
1343
  <div className="mt-2.5 px-3 pb-3">
1332
1344
  <div className="grid grid-cols-[4.5rem_1fr_3rem] items-center gap-1">
1333
- {/* Model pill — bare text+chevron, no border or bg box (Figma-style) */}
1345
+ {/* Model pill — bare text+chevron, no border or bg box (design-editor) */}
1334
1346
  <ColorModelPill
1335
1347
  value={mode}
1336
1348
  disabled={disabled}
1337
- onChange={(v) => setMode(v as FigmaColorMode)}
1349
+ onChange={(v) => setMode(v as DesignColorMode)}
1338
1350
  />
1339
1351
 
1340
1352
  {/* Value field(s) — adapts to mode */}
@@ -1379,9 +1391,9 @@ export function FigmaColorPicker({
1379
1391
  otherwise falls back to the single current color so the
1380
1392
  section is never empty. */}
1381
1393
  <div className="border-t border-border/70 px-3 py-2.5">
1382
- {/* Source label — matches Figma layout */}
1394
+ {/* Source label — matches the design editor layout */}
1383
1395
  <div className="mb-2 flex h-6 w-full items-center justify-between px-0.5 text-[11px] text-muted-foreground">
1384
- {"Document colors" /* i18n-ignore Figma picker source */}
1396
+ {"Document colors" /* i18n-ignore design picker source */}
1385
1397
  </div>
1386
1398
 
1387
1399
  {/* Swatch grid: document palette when available, else current color */}
@@ -1434,7 +1446,7 @@ export function FigmaColorPicker({
1434
1446
 
1435
1447
  // ─── Sub-components ────────────────────────────────────────────────────────────
1436
1448
 
1437
- const COLOR_MODES: Array<{ value: FigmaColorMode; label: string }> = [
1449
+ const COLOR_MODES: Array<{ value: DesignColorMode; label: string }> = [
1438
1450
  { value: "hex", label: "Hex" }, // i18n-ignore color mode
1439
1451
  { value: "rgb", label: "RGB" }, // i18n-ignore color mode
1440
1452
  { value: "hsl", label: "HSL" }, // i18n-ignore color mode
@@ -1442,7 +1454,7 @@ const COLOR_MODES: Array<{ value: FigmaColorMode; label: string }> = [
1442
1454
  ];
1443
1455
 
1444
1456
  /**
1445
- * Figma-style bare color-model selector: text + small chevron, no border/bg box.
1457
+ * design-editor bare color-model selector: text + small chevron, no border/bg box.
1446
1458
  * Hover reveals a subtle bg tint; active mode is font-semibold.
1447
1459
  * Renders its own lightweight dropdown (no Radix Select overhead).
1448
1460
  */
@@ -1451,9 +1463,9 @@ function ColorModelPill({
1451
1463
  disabled,
1452
1464
  onChange,
1453
1465
  }: {
1454
- value: FigmaColorMode;
1466
+ value: DesignColorMode;
1455
1467
  disabled: boolean;
1456
- onChange: (mode: FigmaColorMode) => void;
1468
+ onChange: (mode: DesignColorMode) => void;
1457
1469
  }) {
1458
1470
  const [menuOpen, setMenuOpen] = useState(false);
1459
1471
  const containerRef = useRef<HTMLDivElement>(null);
@@ -1720,7 +1732,7 @@ function ColorTrack({
1720
1732
  )}
1721
1733
  style={{ backgroundImage, backgroundSize, backgroundPosition }}
1722
1734
  >
1723
- {/* Thumb overhangs the track slightly, matching Figma */}
1735
+ {/* Thumb overhangs the track slightly, matching the design editor */}
1724
1736
  <span
1725
1737
  className="pointer-events-none absolute top-1/2 size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-[0_0_0_1px_hsl(var(--foreground)/0.6)]"
1726
1738
  style={{ left: `${clamp(percent, 0, 100)}%` }}
@@ -1799,12 +1811,16 @@ function ScrubbyNumberInput({
1799
1811
  if (e.key === "ArrowUp") {
1800
1812
  e.preventDefault();
1801
1813
  const step = e.shiftKey ? 10 : 1;
1802
- onChange(clamp(value + step, min, max));
1814
+ const parsed = Number(draft);
1815
+ const base = Number.isFinite(parsed) ? parsed : value;
1816
+ onChange(clamp(base + step, min, max));
1803
1817
  }
1804
1818
  if (e.key === "ArrowDown") {
1805
1819
  e.preventDefault();
1806
1820
  const step = e.shiftKey ? 10 : 1;
1807
- onChange(clamp(value - step, min, max));
1821
+ const parsed = Number(draft);
1822
+ const base = Number.isFinite(parsed) ? parsed : value;
1823
+ onChange(clamp(base - step, min, max));
1808
1824
  }
1809
1825
  }}
1810
1826
  onBlur={() => {
@@ -1820,11 +1836,22 @@ function ScrubbyNumberInput({
1820
1836
 
1821
1837
  // ─── Utilities ─────────────────────────────────────────────────────────────────
1822
1838
 
1823
- export function inferPaintType(value: string, opacity: number): FigmaPaintType {
1839
+ export function inferPaintType(
1840
+ value: string,
1841
+ opacity: number,
1842
+ ): DesignPaintType {
1824
1843
  const lower = value.trim().toLowerCase();
1825
1844
  if (lower.includes("gradient(")) {
1826
- if (lower.startsWith("radial-gradient")) return "radial";
1827
- if (lower.startsWith("conic-gradient")) return "angular";
1845
+ if (
1846
+ lower.startsWith("radial-gradient") ||
1847
+ lower.startsWith("repeating-radial-gradient")
1848
+ )
1849
+ return "radial";
1850
+ if (
1851
+ lower.startsWith("conic-gradient") ||
1852
+ lower.startsWith("repeating-conic-gradient")
1853
+ )
1854
+ return "angular";
1828
1855
  return "linear";
1829
1856
  }
1830
1857
  if (lower.startsWith("url(")) return "image";
@@ -1836,7 +1863,7 @@ export function inferPaintType(value: string, opacity: number): FigmaPaintType {
1836
1863
  }
1837
1864
 
1838
1865
  /** The set of paint types that render a gradient editor. */
1839
- export const GRADIENT_PAINT_TYPES: ReadonlySet<FigmaPaintType> = new Set([
1866
+ export const GRADIENT_PAINT_TYPES: ReadonlySet<DesignPaintType> = new Set([
1840
1867
  "linear",
1841
1868
  "radial",
1842
1869
  "angular",
@@ -1860,17 +1887,17 @@ export const GRADIENT_PAINT_TYPES: ReadonlySet<FigmaPaintType> = new Set([
1860
1887
  * @param opacity The current opacity (0–100).
1861
1888
  */
1862
1889
  export function resolveActivePaint(
1863
- paintType: FigmaPaintType | undefined,
1864
- localPaintType: FigmaPaintType | null,
1890
+ paintType: DesignPaintType | undefined,
1891
+ localPaintType: DesignPaintType | null,
1865
1892
  value: string,
1866
1893
  opacity: number,
1867
1894
  ): {
1868
- effectivePaintType: FigmaPaintType;
1895
+ effectivePaintType: DesignPaintType;
1869
1896
  showGradientEditor: boolean;
1870
1897
  showImageControls: boolean;
1871
1898
  showShaderPanel: boolean;
1872
1899
  } {
1873
- const effectivePaintType: FigmaPaintType =
1900
+ const effectivePaintType: DesignPaintType =
1874
1901
  localPaintType ?? paintType ?? inferPaintType(value, opacity);
1875
1902
  return {
1876
1903
  effectivePaintType,
@@ -1884,12 +1911,12 @@ function toCssColor(color: RgbaColor): string {
1884
1911
  return rgbaToCss(color);
1885
1912
  }
1886
1913
 
1887
- /** Show hex without the leading # for the display field (matches Figma). */
1914
+ /** Show hex without the leading # for the display field (matches the design editor). */
1888
1915
  function toDisplayHex(color: RgbaColor): string {
1889
1916
  return rgbaToHex(color).replace(/^#/, "").toUpperCase();
1890
1917
  }
1891
1918
 
1892
- function triggerLabel(type: FigmaPaintType, color: RgbaColor): string {
1919
+ function triggerLabel(type: DesignPaintType, color: RgbaColor): string {
1893
1920
  if (type === "solid") return toDisplayHex(color);
1894
1921
  if (type === "none") return "None";
1895
1922
  if (type === "image") return "Image";
@@ -1,5 +1,5 @@
1
1
  import { IconDownload, IconPlus, IconX } from "@tabler/icons-react";
2
- import { useState } from "react";
2
+ import { useEffect, useState } from "react";
3
3
 
4
4
  import { Button } from "@/components/ui/button";
5
5
  import { Input } from "@/components/ui/input";
@@ -37,7 +37,7 @@ export interface ExportSettingsPanelLabels {
37
37
  export interface ExportSettingsPanelProps {
38
38
  value: ExportSettingsValue;
39
39
  onChange: (patch: Partial<ExportSettingsValue>) => void;
40
- onExport: (settings: ExportSettingsValue) => void;
40
+ onExport: (settings: ExportSettingsValue[]) => void;
41
41
  formats?: ExportFormat[];
42
42
  labels?: Partial<ExportSettingsPanelLabels>;
43
43
  disabled?: boolean;
@@ -57,7 +57,7 @@ const DEFAULT_LABELS: ExportSettingsPanelLabels = {
57
57
 
58
58
  const DEFAULT_FORMATS: ExportFormat[] = ["png", "jpg", "svg", "pdf", "webp"];
59
59
 
60
- /** Preset scale options shown in the scale dropdown — matches Figma's presets */
60
+ /** Preset scale options shown in the scale dropdown — matches the design editor's presets */
61
61
  const SCALE_PRESETS: { label: string; value: ExportScale }[] = [
62
62
  { label: "0.5x", value: "0.5" },
63
63
  { label: "1x", value: "1" },
@@ -103,7 +103,7 @@ function rowFromValue(v: ExportSettingsValue): ExportRow {
103
103
  };
104
104
  }
105
105
 
106
- /** Scale dropdown — shows Figma's 0.5x/1x/2x/3x/4x presets + Custom */
106
+ /** Scale dropdown — shows the design editor's 0.5x/1x/2x/3x/4x presets + Custom */
107
107
  function ScaleSelect({
108
108
  scale,
109
109
  customScale,
@@ -162,15 +162,7 @@ export function ExportSettingsPanel({
162
162
  // Multi-row internal state — primary row is kept in sync with the `value` prop
163
163
  const [rows, setRows] = useState<ExportRow[]>(() => [rowFromValue(value)]);
164
164
 
165
- // Keep primary row in sync when the controlled value prop changes externally
166
- const primaryValue = rows[0];
167
- const needsSync =
168
- !primaryValue.customScale &&
169
- (primaryValue.scale !== value.scale ||
170
- primaryValue.format !== value.format ||
171
- primaryValue.suffix !== value.suffix);
172
-
173
- if (needsSync) {
165
+ useEffect(() => {
174
166
  setRows((prev) => [
175
167
  {
176
168
  ...prev[0],
@@ -180,7 +172,7 @@ export function ExportSettingsPanel({
180
172
  },
181
173
  ...prev.slice(1),
182
174
  ]);
183
- }
175
+ }, [value.format, value.scale, value.suffix]);
184
176
 
185
177
  function patchRow(id: number, patch: Partial<ExportRow>) {
186
178
  setRows((prev) => prev.map((r) => (r.id === id ? { ...r, ...patch } : r)));
@@ -226,19 +218,15 @@ export function ExportSettingsPanel({
226
218
  }
227
219
 
228
220
  function handleExport() {
229
- // Pass the primary row's settings to onExport; rows array is internal
230
- const primary = rows[0];
231
- if (!primary) return;
232
- onExport({
233
- scale: primary.scale,
234
- format: primary.format,
235
- suffix: primary.suffix,
236
- });
221
+ if (rows.length === 0) return;
222
+ onExport(
223
+ rows.map((r) => ({ scale: r.scale, format: r.format, suffix: r.suffix })),
224
+ );
237
225
  }
238
226
 
239
227
  return (
240
228
  <div className={cn("space-y-1.5", className)}>
241
- {/* Section header: title left, "+" right — matches Figma export header */}
229
+ {/* Section header: title left, "+" right — matches the design editor export header */}
242
230
  <div className="flex h-6 items-center justify-between">
243
231
  <span className="text-[11px] font-medium text-muted-foreground">
244
232
  {copy.title}
@@ -254,7 +242,7 @@ export function ExportSettingsPanel({
254
242
  </button>
255
243
  </div>
256
244
 
257
- {/* Export rows: [scale ▾] [format ▾] [suffix] [×] — Figma compact inline layout */}
245
+ {/* Export rows: [scale ▾] [format ▾] [suffix] [×] — design-editor compact inline layout */}
258
246
  {rows.map((row) => (
259
247
  <ExportRow
260
248
  key={row.id}
@@ -268,7 +256,7 @@ export function ExportSettingsPanel({
268
256
  />
269
257
  ))}
270
258
 
271
- {/* Export button — full width at bottom, Figma style */}
259
+ {/* Export button — full width at bottom, design-editor style */}
272
260
  <Button
273
261
  type="button"
274
262
  variant="outline"
@@ -380,7 +368,7 @@ function ExportRow({
380
368
  aria-label={labels.suffix}
381
369
  />
382
370
 
383
- {/* Remove row button — matches Figma's × on each export entry */}
371
+ {/* Remove row button — matches the design editor's × on each export entry */}
384
372
  <button
385
373
  type="button"
386
374
  aria-label={labels.removeExport}
@@ -27,7 +27,7 @@ export interface GradientValue {
27
27
  stops: GradientStopValue[];
28
28
  }
29
29
 
30
- // ─── Checkerboard (matches FigmaColorPicker) ───────────────────────────────────
30
+ // ─── Checkerboard (matches DesignColorPicker) ───────────────────────────────────
31
31
 
32
32
  const CHECKER_A = "#d4d4d4";
33
33
  const CHECKERBOARD_IMAGE = `linear-gradient(45deg, ${CHECKER_A} 25%, transparent 25%), linear-gradient(-45deg, ${CHECKER_A} 25%, transparent 25%), linear-gradient(45deg, transparent 75%, ${CHECKER_A} 75%), linear-gradient(-45deg, transparent 75%, ${CHECKER_A} 75%)`;
@@ -53,7 +53,7 @@ export function gradientToCss(value: GradientValue): string {
53
53
  return `radial-gradient(circle at center, ${stops})`;
54
54
  case "diamond":
55
55
  // CSS has no diamond gradient; a radial gradient with closest-side on a
56
- // non-circular ellipse reads as the diamond falloff Figma shows.
56
+ // non-circular ellipse reads as the diamond falloff the design editor shows.
57
57
  return `radial-gradient(ellipse closest-side at center, ${stops})`;
58
58
  case "angular":
59
59
  return `conic-gradient(from ${round(value.angle)}deg at center, ${stops})`;
@@ -155,7 +155,7 @@ export function parseGradientCss(
155
155
  : false;
156
156
 
157
157
  if (fn === "linear") {
158
- kind = fallbackKind === "linear" ? "linear" : fallbackKind;
158
+ kind = "linear";
159
159
  const angleMatch = first.match(ANGLE_RE);
160
160
  if (angleMatch) {
161
161
  angle = Number(angleMatch[1]);
@@ -166,7 +166,10 @@ export function parseGradientCss(
166
166
  stopStart = 1;
167
167
  }
168
168
  } else if (fn === "radial") {
169
- kind = /ellipse/i.test(first) ? "diamond" : "radial";
169
+ kind =
170
+ /ellipse\s+closest-side/i.test(first) || fallbackKind === "diamond"
171
+ ? "diamond"
172
+ : "radial";
170
173
  if (/circle|ellipse|at\s/i.test(first)) stopStart = 1;
171
174
  } else if (fn === "conic") {
172
175
  kind = "angular";
@@ -199,7 +202,7 @@ interface AngleDialProps {
199
202
  disabled?: boolean;
200
203
  }
201
204
 
202
- /** Figma-style circular dial for rotating gradient angle. */
205
+ /** design-editor circular dial for rotating gradient angle. */
203
206
  function AngleDial({ angle, onChange, disabled = false }: AngleDialProps) {
204
207
  const dialRef = useRef<HTMLDivElement>(null);
205
208
  const draggingRef = useRef(false);
@@ -210,7 +213,7 @@ function AngleDial({ angle, onChange, disabled = false }: AngleDialProps) {
210
213
  const cx = rect.left + rect.width / 2;
211
214
  const cy = rect.top + rect.height / 2;
212
215
  const rad = Math.atan2(clientY - cy, clientX - cx);
213
- // atan2 gives angle from east; Figma's 0° is north (up), clockwise.
216
+ // atan2 gives angle from east; the design editor's 0° is north (up), clockwise.
214
217
  let deg = (rad * 180) / Math.PI + 90;
215
218
  if (deg < 0) deg += 360;
216
219
  if (deg >= 360) deg -= 360;
@@ -311,7 +314,7 @@ export interface GradientEditorProps {
311
314
  className?: string;
312
315
  }
313
316
 
314
- // Stop handle dimensions — Figma uses ~12px handles with white ring.
317
+ // Stop handle dimensions — the design editor uses ~12px handles with white ring.
315
318
  const STOP_SIZE = 12; // px, the colored circle diameter
316
319
  const STOP_RING = 2; // px, white border thickness
317
320
  const STOP_OUTER = STOP_SIZE + STOP_RING * 2; // 16px total outer
@@ -372,9 +375,29 @@ export function GradientEditor({
372
375
  barClickRef.current = null;
373
376
  const position = positionFromPointer(event.clientX);
374
377
  const ordered = sortedStops(value.stops);
375
- // Interpolate the color from the nearest stops for a natural insert.
378
+ // Interpolate the color from the adjacent stops for a natural insert.
376
379
  const before = [...ordered].reverse().find((s) => s.position <= position);
377
- const newColor = before?.color ?? ordered[0]?.color ?? "#000000";
380
+ const after = ordered.find((s) => s.position > position);
381
+ let newColor: string;
382
+ if (before && after) {
383
+ const range = after.position - before.position;
384
+ const t = range === 0 ? 0 : (position - before.position) / range;
385
+ const cb = parseCssColor(before.color);
386
+ const ca = parseCssColor(after.color);
387
+ if (cb && ca) {
388
+ newColor = rgbaToCss({
389
+ r: Math.round(cb.r + t * (ca.r - cb.r)),
390
+ g: Math.round(cb.g + t * (ca.g - cb.g)),
391
+ b: Math.round(cb.b + t * (ca.b - cb.b)),
392
+ a: cb.a + t * (ca.a - cb.a),
393
+ });
394
+ } else {
395
+ newColor = before.color;
396
+ }
397
+ } else {
398
+ newColor =
399
+ before?.color ?? after?.color ?? ordered[0]?.color ?? "#000000";
400
+ }
378
401
  const newStop: GradientStopValue = {
379
402
  id: nextStopId(),
380
403
  color: newColor,
@@ -501,7 +524,7 @@ export function GradientEditor({
501
524
  "absolute cursor-grab active:cursor-grabbing",
502
525
  "rounded-full border-[2px] border-white",
503
526
  "focus-visible:outline-none",
504
- // Selected: accent-colored outer ring (like Figma's blue ring)
527
+ // Selected: accent-colored outer ring (the same way's blue ring)
505
528
  isSelected
506
529
  ? "shadow-[0_0_0_1.5px_var(--primary),0_1px_3px_rgba(0,0,0,0.35)]"
507
530
  : "shadow-[0_0_0_1px_rgba(0,0,0,0.25),0_1px_3px_rgba(0,0,0,0.25)]",
@@ -560,9 +583,14 @@ export function GradientEditor({
560
583
  disabled={disabled}
561
584
  value={angleInput ?? Math.round(value.angle)}
562
585
  onChange={(e) => {
563
- setAngleInput(e.target.value);
564
586
  const next = Number(e.target.value);
565
- if (Number.isFinite(next)) setAngle(next);
587
+ if (Number.isFinite(next)) {
588
+ const normalised = ((next % 360) + 360) % 360;
589
+ setAngle(next);
590
+ setAngleInput(String(Math.round(normalised)));
591
+ } else {
592
+ setAngleInput(e.target.value);
593
+ }
566
594
  }}
567
595
  onBlur={() => setAngleInput(null)}
568
596
  className="h-full min-w-0 flex-1 bg-transparent px-1.5 text-[11px] tabular-nums focus-visible:outline-none"