@agent-native/core 0.92.11 → 0.93.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (777) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +45 -0
  3. package/corpus/core/docs/content/automation-connectors.mdx +80 -0
  4. package/corpus/core/docs/content/extensions.mdx +8 -0
  5. package/corpus/core/docs/content/locales/ar-SA/extensions.mdx +8 -0
  6. package/corpus/core/docs/content/locales/ar-SA/messaging.mdx +84 -15
  7. package/corpus/core/docs/content/locales/ar-SA/template-clips.mdx +1 -0
  8. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +1 -0
  9. package/corpus/core/docs/content/locales/de-DE/extensions.mdx +10 -0
  10. package/corpus/core/docs/content/locales/de-DE/messaging.mdx +92 -15
  11. package/corpus/core/docs/content/locales/de-DE/template-clips.mdx +1 -0
  12. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +1 -0
  13. package/corpus/core/docs/content/locales/es-ES/extensions.mdx +10 -0
  14. package/corpus/core/docs/content/locales/es-ES/messaging.mdx +91 -15
  15. package/corpus/core/docs/content/locales/es-ES/template-clips.mdx +1 -0
  16. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +1 -0
  17. package/corpus/core/docs/content/locales/fr-FR/extensions.mdx +10 -0
  18. package/corpus/core/docs/content/locales/fr-FR/messaging.mdx +91 -15
  19. package/corpus/core/docs/content/locales/fr-FR/template-clips.mdx +1 -0
  20. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +1 -0
  21. package/corpus/core/docs/content/locales/hi-IN/extensions.mdx +8 -0
  22. package/corpus/core/docs/content/locales/hi-IN/messaging.mdx +81 -15
  23. package/corpus/core/docs/content/locales/hi-IN/template-clips.mdx +1 -0
  24. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +1 -0
  25. package/corpus/core/docs/content/locales/ja-JP/extensions.mdx +9 -0
  26. package/corpus/core/docs/content/locales/ja-JP/messaging.mdx +81 -15
  27. package/corpus/core/docs/content/locales/ja-JP/template-clips.mdx +1 -0
  28. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +1 -0
  29. package/corpus/core/docs/content/locales/ko-KR/extensions.mdx +8 -0
  30. package/corpus/core/docs/content/locales/ko-KR/messaging.mdx +80 -15
  31. package/corpus/core/docs/content/locales/ko-KR/template-clips.mdx +1 -0
  32. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +1 -0
  33. package/corpus/core/docs/content/locales/pt-BR/extensions.mdx +9 -0
  34. package/corpus/core/docs/content/locales/pt-BR/messaging.mdx +88 -15
  35. package/corpus/core/docs/content/locales/pt-BR/template-clips.mdx +1 -0
  36. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +1 -0
  37. package/corpus/core/docs/content/locales/zh-CN/extensions.mdx +6 -0
  38. package/corpus/core/docs/content/locales/zh-CN/messaging.mdx +78 -15
  39. package/corpus/core/docs/content/locales/zh-CN/template-clips.mdx +1 -0
  40. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +1 -0
  41. package/corpus/core/docs/content/locales/zh-TW/extensions.mdx +6 -0
  42. package/corpus/core/docs/content/locales/zh-TW/messaging.mdx +78 -15
  43. package/corpus/core/docs/content/locales/zh-TW/template-clips.mdx +1 -0
  44. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +1 -0
  45. package/corpus/core/docs/content/messaging.mdx +227 -45
  46. package/corpus/core/docs/content/template-clips.mdx +3 -0
  47. package/corpus/core/docs/content/template-design.mdx +4 -0
  48. package/corpus/core/package.json +5 -1
  49. package/corpus/core/src/a2a/artifact-response.ts +131 -2
  50. package/corpus/core/src/a2a/client.ts +10 -0
  51. package/corpus/core/src/a2a/handlers.ts +40 -76
  52. package/corpus/core/src/a2a/types.ts +2 -0
  53. package/corpus/core/src/agent/durable-background.ts +24 -0
  54. package/corpus/core/src/agent/production-agent.ts +48 -5
  55. package/corpus/core/src/agent/run-manager.ts +171 -20
  56. package/corpus/core/src/agent/run-store.ts +297 -42
  57. package/corpus/core/src/agent/types.ts +23 -0
  58. package/corpus/core/src/audit/record.ts +31 -0
  59. package/corpus/core/src/audit/store.ts +69 -3
  60. package/corpus/core/src/audit/types.ts +13 -0
  61. package/corpus/core/src/automation/index.ts +802 -0
  62. package/corpus/core/src/cli/create.ts +12 -5
  63. package/corpus/core/src/cli/design-connect.ts +596 -36
  64. package/corpus/core/src/client/AgentPanel.tsx +51 -6
  65. package/corpus/core/src/client/AssistantChat.tsx +274 -147
  66. package/corpus/core/src/client/MultiTabAssistantChat.tsx +3 -0
  67. package/corpus/core/src/client/RunStuckBanner.tsx +69 -20
  68. package/corpus/core/src/client/agent-chat-adapter.ts +110 -17
  69. package/corpus/core/src/client/automation.ts +22 -0
  70. package/corpus/core/src/client/chat/run-recovery.tsx +67 -50
  71. package/corpus/core/src/client/composer/TiptapComposer.tsx +8 -0
  72. package/corpus/core/src/client/index.ts +30 -0
  73. package/corpus/core/src/client/integrations/api.ts +295 -0
  74. package/corpus/core/src/client/integrations/index.ts +26 -0
  75. package/corpus/core/src/client/transcription/use-live-transcription.ts +68 -5
  76. package/corpus/core/src/client/use-run-stuck-detection.ts +41 -3
  77. package/corpus/core/src/deploy/build.ts +59 -3
  78. package/corpus/core/src/deploy/workspace-deploy.ts +37 -2
  79. package/corpus/core/src/file-upload/types.ts +2 -0
  80. package/corpus/core/src/index.ts +37 -0
  81. package/corpus/core/src/integrations/a2a-continuations-store.ts +6 -4
  82. package/corpus/core/src/integrations/adapters/discord.ts +362 -0
  83. package/corpus/core/src/integrations/adapters/index.ts +29 -0
  84. package/corpus/core/src/integrations/adapters/microsoft-teams.ts +411 -0
  85. package/corpus/core/src/integrations/adapters/slack.ts +861 -27
  86. package/corpus/core/src/integrations/adapters/telegram.ts +54 -15
  87. package/corpus/core/src/integrations/adapters/whatsapp.ts +41 -17
  88. package/corpus/core/src/integrations/catalog.ts +571 -0
  89. package/corpus/core/src/integrations/controls-store.ts +187 -0
  90. package/corpus/core/src/integrations/index.ts +100 -0
  91. package/corpus/core/src/integrations/installations-store.ts +677 -0
  92. package/corpus/core/src/integrations/integration-memory.ts +187 -0
  93. package/corpus/core/src/integrations/pending-tasks-retry-job.ts +6 -6
  94. package/corpus/core/src/integrations/pending-tasks-store.ts +83 -5
  95. package/corpus/core/src/integrations/plugin.ts +685 -47
  96. package/corpus/core/src/integrations/scope-store.ts +498 -0
  97. package/corpus/core/src/integrations/slack-manifest.ts +78 -0
  98. package/corpus/core/src/integrations/slack-oauth.ts +343 -0
  99. package/corpus/core/src/integrations/types.ts +179 -0
  100. package/corpus/core/src/integrations/usage-budget-store.ts +906 -0
  101. package/corpus/core/src/integrations/webhook-handler.ts +562 -51
  102. package/corpus/core/src/jobs/scheduler.ts +100 -2
  103. package/corpus/core/src/jobs/tools.ts +45 -9
  104. package/corpus/core/src/provider-api/index.ts +72 -1
  105. package/corpus/core/src/resources/handlers.ts +88 -14
  106. package/corpus/core/src/resources/script-helpers.ts +36 -7
  107. package/corpus/core/src/resources/store.ts +59 -52
  108. package/corpus/core/src/scripts/call-agent.ts +107 -1
  109. package/corpus/core/src/server/agent-chat-plugin.ts +294 -119
  110. package/corpus/core/src/server/auth.ts +10 -14
  111. package/corpus/core/src/server/builder-design-systems.ts +16 -1
  112. package/corpus/core/src/server/edge.ts +18 -0
  113. package/corpus/core/src/server/index.ts +26 -0
  114. package/corpus/core/src/server/request-context.ts +18 -0
  115. package/corpus/core/src/server/social-og-image.ts +3 -2
  116. package/corpus/core/src/styles/agent-native.css +62 -44
  117. package/corpus/core/src/templates/default/.agents/skills/capture-learnings/SKILL.md +22 -9
  118. package/corpus/core/src/templates/default/AGENTS.md +7 -3
  119. package/corpus/core/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +1 -1
  120. package/corpus/core/src/templates/workspace-core/.agents/skills/capture-learnings/SKILL.md +22 -9
  121. package/corpus/core/src/templates/workspace-core/.agents/skills/extension-points/SKILL.md +6 -3
  122. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +25 -0
  123. package/corpus/core/src/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +15 -0
  124. package/corpus/core/src/templates/workspace-core/AGENTS.md +3 -2
  125. package/corpus/core/src/usage/store.ts +37 -2
  126. package/corpus/templates/analytics/.agents/skills/capture-learnings/SKILL.md +22 -9
  127. package/corpus/templates/analytics/.agents/skills/provider-api/SKILL.md +23 -0
  128. package/corpus/templates/analytics/.builder/skills/provider-api/SKILL.md +15 -0
  129. package/corpus/templates/analytics/actions/save-monitor.ts +25 -2
  130. package/corpus/templates/analytics/app/lib/data-sources.ts +28 -0
  131. package/corpus/templates/analytics/changelog/2026-07-10-agent-created-uptime-monitors-now-return-a-direct-link-to-th.md +6 -0
  132. package/corpus/templates/analytics/changelog/2026-07-10-dashboard-email-reports-still-arrive-when-screenshot-capture-temporarily-fails.md +6 -0
  133. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +3 -7
  134. package/corpus/templates/analytics/server/lib/credential-keys.ts +12 -0
  135. package/corpus/templates/analytics/server/lib/dashboard-report.ts +5 -1
  136. package/corpus/templates/analytics/server/lib/provider-credentials.ts +2 -0
  137. package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +16 -0
  138. package/corpus/templates/assets/.agents/skills/capture-learnings/SKILL.md +22 -9
  139. package/corpus/templates/brain/.agents/skills/capture-learnings/SKILL.md +22 -9
  140. package/corpus/templates/calendar/.agents/skills/capture-learnings/SKILL.md +22 -9
  141. package/corpus/templates/calendar/app/components/calendar/CommandPalette.tsx +98 -108
  142. package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +97 -90
  143. package/corpus/templates/calendar/changelog/2026-07-10-event-guest-details-and-option-menus-now-use-valid-accessibl.md +6 -0
  144. package/corpus/templates/calendar/changelog/2026-07-10-natural-language-event-phrases-stay-available-as-quick-creat.md +6 -0
  145. package/corpus/templates/chat/.agents/skills/capture-learnings/SKILL.md +22 -9
  146. package/corpus/templates/chat/changelog/2026-07-10-chat-now-makes-ai-connection-setup-clear-without-shifting-th.md +6 -0
  147. package/corpus/templates/clips/.agents/skills/capture-learnings/SKILL.md +22 -9
  148. package/corpus/templates/clips/.agents/skills/recording/SKILL.md +17 -1
  149. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +8 -0
  150. package/corpus/templates/clips/AGENTS.md +8 -3
  151. package/corpus/templates/clips/actions/finalize-recording.ts +63 -23
  152. package/corpus/templates/clips/actions/get-recording-player-data.ts +6 -2
  153. package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +72 -9
  154. package/corpus/templates/clips/actions/list-recordings.ts +19 -3
  155. package/corpus/templates/clips/actions/navigate.ts +2 -0
  156. package/corpus/templates/clips/actions/reprocess-recording.ts +20 -6
  157. package/corpus/templates/clips/actions/request-transcript.ts +16 -31
  158. package/corpus/templates/clips/actions/view-screen.ts +57 -5
  159. package/corpus/templates/clips/app/components/layout/Header.tsx +1 -0
  160. package/corpus/templates/clips/app/components/library/empty-state.tsx +2 -0
  161. package/corpus/templates/clips/app/components/library/library-grid.tsx +69 -42
  162. package/corpus/templates/clips/app/components/library/library-layout.tsx +15 -1
  163. package/corpus/templates/clips/app/components/library/recording-card.tsx +81 -68
  164. package/corpus/templates/clips/app/components/library/search-bar.tsx +4 -3
  165. package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +9 -3
  166. package/corpus/templates/clips/app/components/player/share-dialog.tsx +1 -1
  167. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +27 -5
  168. package/corpus/templates/clips/app/hooks/use-library.ts +1 -1
  169. package/corpus/templates/clips/app/hooks/use-navigation-state.ts +10 -2
  170. package/corpus/templates/clips/app/hooks/use-player-shortcuts.ts +1 -0
  171. package/corpus/templates/clips/app/i18n/ar-SA.ts +7 -1
  172. package/corpus/templates/clips/app/i18n/de-DE.ts +7 -1
  173. package/corpus/templates/clips/app/i18n/en-US.ts +7 -1
  174. package/corpus/templates/clips/app/i18n/es-ES.ts +7 -1
  175. package/corpus/templates/clips/app/i18n/fr-FR.ts +7 -1
  176. package/corpus/templates/clips/app/i18n/hi-IN.ts +7 -1
  177. package/corpus/templates/clips/app/i18n/ja-JP.ts +7 -1
  178. package/corpus/templates/clips/app/i18n/ko-KR.ts +7 -1
  179. package/corpus/templates/clips/app/i18n/pt-BR.ts +7 -1
  180. package/corpus/templates/clips/app/i18n/zh-CN.ts +8 -1
  181. package/corpus/templates/clips/app/i18n/zh-TW.ts +8 -1
  182. package/corpus/templates/clips/app/lib/recording-visibility.ts +70 -0
  183. package/corpus/templates/clips/app/routes/_app.shared.tsx +20 -0
  184. package/corpus/templates/clips/app/routes/download.tsx +24 -8
  185. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +115 -2
  186. package/corpus/templates/clips/app/routes/record.tsx +62 -0
  187. package/corpus/templates/clips/app/routes/share.$shareId.tsx +25 -16
  188. package/corpus/templates/clips/changelog/2026-07-10-clips-desktop-is-now-available-for-linux-with-appimage-debia.md +6 -0
  189. package/corpus/templates/clips/changelog/2026-07-10-clips-downloads-updates-sooner-in-the-background-and-offers-.md +6 -0
  190. package/corpus/templates/clips/changelog/2026-07-10-clips-shared-with-you-now-appear-in-a-searchable-shared-with.md +6 -0
  191. package/corpus/templates/clips/changelog/2026-07-10-clips-with-missing-mobile-video-frames-can-be-repaired-witho.md +6 -0
  192. package/corpus/templates/clips/changelog/2026-07-10-fixed-copying-agent-responses-and-transcript-text-from-recor.md +6 -0
  193. package/corpus/templates/clips/changelog/2026-07-10-mobile-camera-recordings-pause-while-backgrounded.md +6 -0
  194. package/corpus/templates/clips/changelog/2026-07-10-mp4-downloads-now-show-progress-immediately-after-the-option.md +6 -0
  195. package/corpus/templates/clips/changelog/2026-07-10-recording-pages-now-open-as-soon-as-you-stop-with-a-share-li.md +6 -0
  196. package/corpus/templates/clips/changelog/2026-07-10-recordings-now-seek-smoothly-downloads-use-the-correct-file-.md +6 -0
  197. package/corpus/templates/clips/changelog/2026-07-10-s3-compatible-storage-now-saves-hosted-recordings-without-re.md +6 -0
  198. package/corpus/templates/clips/desktop/README.md +22 -6
  199. package/corpus/templates/clips/desktop/package.json +1 -1
  200. package/corpus/templates/clips/desktop/src/app.tsx +32 -19
  201. package/corpus/templates/clips/desktop/src/components/ReadinessPanel.tsx +15 -9
  202. package/corpus/templates/clips/desktop/src/lib/finalization-guard.ts +22 -0
  203. package/corpus/templates/clips/desktop/src/lib/permissions.ts +3 -1
  204. package/corpus/templates/clips/desktop/src/lib/recorder.ts +97 -120
  205. package/corpus/templates/clips/desktop/src/lib/updater.ts +26 -4
  206. package/corpus/templates/clips/desktop/src/overlays/finalizing.tsx +4 -8
  207. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +1 -1
  208. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +2 -2
  209. package/corpus/templates/clips/desktop/src-tauri/src/permission_status.rs +11 -7
  210. package/corpus/templates/clips/desktop/src-tauri/tauri.linux.conf.json +11 -0
  211. package/corpus/templates/clips/netlify.toml +3 -0
  212. package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +106 -0
  213. package/corpus/templates/clips/server/lib/public-agent-context.ts +9 -1
  214. package/corpus/templates/clips/server/lib/resumable-upload-provider.ts +52 -0
  215. package/corpus/templates/clips/server/lib/s3-upload-provider.ts +433 -85
  216. package/corpus/templates/clips/server/lib/video-remux.ts +114 -0
  217. package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +109 -0
  218. package/corpus/templates/clips/server/routes/api/clips-updater.json.get.ts +22 -11
  219. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +4 -2
  220. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +47 -5
  221. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +10 -2
  222. package/corpus/templates/clips/shared/share-loader-response.ts +12 -0
  223. package/corpus/templates/clips/shared/transcript-status.ts +40 -0
  224. package/corpus/templates/content/.agents/skills/capture-learnings/SKILL.md +22 -9
  225. package/corpus/templates/content/.agents/skills/content/SKILL.md +57 -5
  226. package/corpus/templates/content/AGENTS.md +65 -55
  227. package/corpus/templates/content/actions/_builder-cms-read-client.ts +92 -23
  228. package/corpus/templates/content/actions/_database-source-utils.ts +207 -41
  229. package/corpus/templates/content/actions/_property-utils.ts +30 -2
  230. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +316 -121
  231. package/corpus/templates/content/actions/attach-content-database-source.ts +41 -17
  232. package/corpus/templates/content/actions/change-content-database-source-role.ts +46 -8
  233. package/corpus/templates/content/actions/submit-content-database-form.ts +497 -0
  234. package/corpus/templates/content/actions/suggest-source-join-key.ts +8 -1
  235. package/corpus/templates/content/actions/update-content-database-view.ts +8 -1
  236. package/corpus/templates/content/actions/view-screen.ts +4 -1
  237. package/corpus/templates/content/agent-native.app-skill.json +1 -1
  238. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +250 -64
  239. package/corpus/templates/content/app/components/editor/database/FormView.tsx +460 -0
  240. package/corpus/templates/content/app/components/editor/database/navigation-state.ts +3 -0
  241. package/corpus/templates/content/app/components/editor/database/settings.tsx +30 -37
  242. package/corpus/templates/content/app/components/editor/database/shared.tsx +2 -0
  243. package/corpus/templates/content/app/components/editor/database/view-config.ts +29 -1
  244. package/corpus/templates/content/app/components/ui/textarea.tsx +1 -0
  245. package/corpus/templates/content/app/hooks/use-content-database.ts +22 -0
  246. package/corpus/templates/content/app/i18n/zh-TW.ts +21 -0
  247. package/corpus/templates/content/app/i18n-data.ts +225 -0
  248. package/corpus/templates/content/changelog/2026-07-09-builder-source-refreshes-now-keep-mapped-fields-such-as-topi.md +6 -0
  249. package/corpus/templates/content/changelog/2026-07-10-builder-backed-properties-now-load-current-source-values-as-.md +6 -0
  250. package/corpus/templates/content/changelog/2026-07-10-builder-database-refreshes-now-stay-explicit-and-accurate.md +6 -0
  251. package/corpus/templates/content/changelog/2026-07-10-database-form-views-now-collect-ordered-required-answers-and.md +6 -0
  252. package/corpus/templates/content/changelog/2026-07-10-database-tables-now-use-a-quieter-more-compact-layout-with-c.md +6 -0
  253. package/corpus/templates/content/changelog/2026-07-10-slack-intake-routes-to-content-database-forms.md +6 -0
  254. package/corpus/templates/content/parity/matrix.md +1 -0
  255. package/corpus/templates/content/parity/matrix.ts +18 -0
  256. package/corpus/templates/content/shared/api.ts +28 -1
  257. package/corpus/templates/content/shared/database-form.ts +53 -0
  258. package/corpus/templates/design/.agents/skills/capture-learnings/SKILL.md +22 -9
  259. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +26 -0
  260. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +85 -12
  261. package/corpus/templates/design/AGENTS.md +72 -18
  262. package/corpus/templates/design/DESIGN.md +1 -1
  263. package/corpus/templates/design/DEVELOPING.md +1 -1
  264. package/corpus/templates/design/FIGMA_INTEROPERABILITY.md +116 -0
  265. package/corpus/templates/design/actions/create-design-from-template.ts +220 -0
  266. package/corpus/templates/design/actions/create-design-system.ts +92 -16
  267. package/corpus/templates/design/actions/delete-design-template.ts +28 -0
  268. package/corpus/templates/design/actions/delete-file.ts +7 -0
  269. package/corpus/templates/design/actions/edit-design.ts +3 -0
  270. package/corpus/templates/design/actions/export-design-as-figma-svg.ts +137 -5
  271. package/corpus/templates/design/actions/export-pdf.ts +1 -1
  272. package/corpus/templates/design/actions/generate-design.ts +3 -0
  273. package/corpus/templates/design/actions/get-design-snapshot.ts +9 -0
  274. package/corpus/templates/design/actions/get-figma-connection-status.ts +26 -0
  275. package/corpus/templates/design/actions/get-figma-design-context.ts +138 -0
  276. package/corpus/templates/design/actions/get-figma-styles.ts +2 -2
  277. package/corpus/templates/design/actions/import-figma-clipboard.ts +112 -16
  278. package/corpus/templates/design/actions/import-figma-frame.ts +15 -4
  279. package/corpus/templates/design/actions/index-design-system-with-builder.ts +4 -0
  280. package/corpus/templates/design/actions/list-design-templates.ts +118 -0
  281. package/corpus/templates/design/actions/navigate.ts +16 -2
  282. package/corpus/templates/design/actions/provider-api-request.ts +11 -1
  283. package/corpus/templates/design/actions/save-design-as-template.ts +156 -0
  284. package/corpus/templates/design/actions/set-active-breakpoint.ts +13 -4
  285. package/corpus/templates/design/actions/take-design-screenshot.ts +73 -3
  286. package/corpus/templates/design/actions/update-file.ts +39 -22
  287. package/corpus/templates/design/actions/view-screen.ts +36 -1
  288. package/corpus/templates/design/actions/write-local-file.ts +50 -38
  289. package/corpus/templates/design/app/components/design/AutoLayoutSuggestionDialog.tsx +151 -0
  290. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +20 -0
  291. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +251 -22
  292. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +3 -3
  293. package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +384 -9
  294. package/corpus/templates/design/app/components/design/EditPanel.tsx +105 -32
  295. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +58 -28
  296. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +6 -0
  297. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +539 -53
  298. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +40 -0
  299. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +1 -0
  300. package/corpus/templates/design/app/components/design/code-workbench/explorer/ExplorerView.tsx +1 -0
  301. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +5 -1
  302. package/corpus/templates/design/app/components/design/code-workbench/workspace/create-providers.ts +2 -0
  303. package/corpus/templates/design/app/components/design/code-workbench/workspace/localhost-provider.ts +3 -1
  304. package/corpus/templates/design/app/components/design/code-workbench/workspace/types.ts +2 -0
  305. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +19 -0
  306. package/corpus/templates/design/app/components/design/edit-panel/frame-presets-panel.tsx +1 -0
  307. package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +27 -0
  308. package/corpus/templates/design/app/components/design/inspector/InteractionStatePanel.tsx +137 -81
  309. package/corpus/templates/design/app/components/design/inspector/frame-size-presets.ts +25 -5
  310. package/corpus/templates/design/app/components/design/multi-screen/chrome-transitions.ts +1 -1
  311. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +220 -3
  312. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +13 -0
  313. package/corpus/templates/design/app/components/design/multi-screen/types.ts +22 -1
  314. package/corpus/templates/design/app/components/design/types.ts +4 -0
  315. package/corpus/templates/design/app/components/design-system/ProductionDesignSystemShowcase.tsx +208 -0
  316. package/corpus/templates/design/app/components/editor/FigmaLinkComposerBubble.tsx +321 -0
  317. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +24 -4
  318. package/corpus/templates/design/app/components/layout/Layout.tsx +16 -2
  319. package/corpus/templates/design/app/components/layout/Sidebar.tsx +2 -0
  320. package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +72 -0
  321. package/corpus/templates/design/app/global.css +2 -35
  322. package/corpus/templates/design/app/hooks/use-navigation-state.ts +10 -0
  323. package/corpus/templates/design/app/i18n/zh-TW.ts +127 -5
  324. package/corpus/templates/design/app/i18n-breakpoints.ts +209 -0
  325. package/corpus/templates/design/app/i18n-data.ts +1317 -59
  326. package/corpus/templates/design/app/i18n-template-feature.ts +458 -0
  327. package/corpus/templates/design/app/lib/design-clipboard.ts +202 -0
  328. package/corpus/templates/design/app/lib/design-file-upload.ts +87 -0
  329. package/corpus/templates/design/app/lib/design-import.ts +168 -11
  330. package/corpus/templates/design/app/lib/desktop-design-preview.ts +423 -0
  331. package/corpus/templates/design/app/lib/figma-clipboard.ts +74 -8
  332. package/corpus/templates/design/app/lib/figma-connection.ts +161 -0
  333. package/corpus/templates/design/app/lib/figma-svg-copy.ts +814 -22
  334. package/corpus/templates/design/app/lib/figma-url.ts +77 -0
  335. package/corpus/templates/design/app/lib/pending-generation.ts +35 -0
  336. package/corpus/templates/design/app/pages/DesignSystems.tsx +236 -177
  337. package/corpus/templates/design/app/pages/Index.tsx +38 -0
  338. package/corpus/templates/design/app/pages/Templates.tsx +498 -0
  339. package/corpus/templates/design/app/pages/design-editor/auto-layout-suggestion.ts +328 -0
  340. package/corpus/templates/design/app/pages/design-editor/clone-idrefs.ts +113 -0
  341. package/corpus/templates/design/app/pages/design-editor/created-screen-navigation.ts +55 -0
  342. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +32 -0
  343. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +49 -2
  344. package/corpus/templates/design/app/pages/design-editor/export-capture.ts +296 -0
  345. package/corpus/templates/design/app/pages/design-editor/history.ts +101 -2
  346. package/corpus/templates/design/app/pages/design-editor/layout-operations.ts +28 -1
  347. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +34 -0
  348. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +202 -6
  349. package/corpus/templates/design/app/pages/design-editor/pending-structure-verification.ts +92 -0
  350. package/corpus/templates/design/app/pages/design-editor/save-failure.ts +58 -0
  351. package/corpus/templates/design/app/routes/templates.tsx +4 -6
  352. package/corpus/templates/design/changelog/2026-07-10-app-workspaces-now-use-clean-borderless-main-surfaces-throug.md +6 -0
  353. package/corpus/templates/design/changelog/2026-07-10-ask-chat-what-s-in-a-figma-file-or-frame-structure-screensho.md +6 -0
  354. package/corpus/templates/design/changelog/2026-07-10-canvas-edits-now-reject-malformed-html-before-it-can-corrupt.md +6 -0
  355. package/corpus/templates/design/changelog/2026-07-10-complex-designs-now-export-as-valid-secure-svgs-and-crisp-ra.md +6 -0
  356. package/corpus/templates/design/changelog/2026-07-10-complex-figma-masks-arcs-rich-text-transformed-images-and-ad.md +6 -0
  357. package/corpus/templates/design/changelog/2026-07-10-copied-layers-paste-across-designs-and-browser-tabs.md +6 -0
  358. package/corpus/templates/design/changelog/2026-07-10-copy-and-download-editable-figma-svg.md +6 -0
  359. package/corpus/templates/design/changelog/2026-07-10-copying-design-text-now-pastes-readable-text-into-other-apps.md +6 -0
  360. package/corpus/templates/design/changelog/2026-07-10-deleted-screens-can-be-restored-with-undo-and-removed-again-.md +6 -0
  361. package/corpus/templates/design/changelog/2026-07-10-design-changes-now-save-before-switching-apps-and-retry-when.md +6 -0
  362. package/corpus/templates/design/changelog/2026-07-10-design-stays-usable-on-phones-by-keeping-editor-panels-and-f.md +6 -0
  363. package/corpus/templates/design/changelog/2026-07-10-download-all-screens-as-one-multi-page-pdf-from-the-overview.md +6 -0
  364. package/corpus/templates/design/changelog/2026-07-10-duplicated-layers-now-keep-labels-aria-relationships-fragmen.md +6 -0
  365. package/corpus/templates/design/changelog/2026-07-10-fig-import-results-stay-compact.md +6 -0
  366. package/corpus/templates/design/changelog/2026-07-10-figma-imports-now-load-real-fonts-fix-rotated-gradient-dashe.md +6 -0
  367. package/corpus/templates/design/changelog/2026-07-10-figma-links-in-chat-and-the-import-tab-now-offer-secure-one-.md +6 -0
  368. package/corpus/templates/design/changelog/2026-07-10-fixed-cmd-z-not-restoring-content-after-an-agent-driven-desi.md +6 -0
  369. package/corpus/templates/design/changelog/2026-07-10-fixed-download-pdf-being-unreachable-from-the-export-panel-e.md +6 -0
  370. package/corpus/templates/design/changelog/2026-07-10-fixed-dragging-a-layer-out-of-an-auto-layout-row-column-no-l.md +6 -0
  371. package/corpus/templates/design/changelog/2026-07-10-fixed-figma-svg-export-text-wrapping-sizing-and-inner-layer.md +6 -0
  372. package/corpus/templates/design/changelog/2026-07-10-fixed-hiding-or-locking-one-layer-no-longer-hides-or-blocks-.md +6 -0
  373. package/corpus/templates/design/changelog/2026-07-10-fixed-png-screenshots-of-tall-screens-being-cropped-to-one-v.md +6 -0
  374. package/corpus/templates/design/changelog/2026-07-10-fixed-png-svg-exports-capturing-broken-layout-when-webfonts-.md +6 -0
  375. package/corpus/templates/design/changelog/2026-07-10-frame-tool-now-offers-standard-ad-unit-size-presets-medium-r.md +6 -0
  376. package/corpus/templates/design/changelog/2026-07-10-full-view-controls-stay-within-their-frame-instead-of-coveri.md +6 -0
  377. package/corpus/templates/design/changelog/2026-07-10-hover-focus-focus-visible-pressed-and-disabled-states-now-pr.md +6 -0
  378. package/corpus/templates/design/changelog/2026-07-10-layer-drops-now-land-in-the-intended-grid-row-stay-visually-.md +6 -0
  379. package/corpus/templates/design/changelog/2026-07-10-local-code-workspaces-now-keep-unsaved-edits-across-panel-sw.md +6 -0
  380. package/corpus/templates/design/changelog/2026-07-10-local-visual-editing-now-keeps-authenticated-sessions-shared.md +6 -0
  381. package/corpus/templates/design/changelog/2026-07-10-long-running-agent-work-no-longer-shows-a-premature-stuck-wa.md +6 -0
  382. package/corpus/templates/design/changelog/2026-07-10-managed-figma-connections-are-recognized-automatically.md +6 -0
  383. package/corpus/templates/design/changelog/2026-07-10-new-designs-can-open-directly-in-the-editor-without-a-pr.md +6 -0
  384. package/corpus/templates/design/changelog/2026-07-10-new-screens-are-selected-and-revealed-immediately.md +6 -0
  385. package/corpus/templates/design/changelog/2026-07-10-new-text-layers-undo-as-one-safe-creation.md +6 -0
  386. package/corpus/templates/design/changelog/2026-07-10-preview-and-apply-inferred-auto-layout-without-changing-yo.md +5 -0
  387. package/corpus/templates/design/changelog/2026-07-10-print-pdf-exports-and-paper-size-presets-letter-a4-now-match.md +6 -0
  388. package/corpus/templates/design/changelog/2026-07-10-responsive-frame-editing.md +6 -0
  389. package/corpus/templates/design/changelog/2026-07-10-reusable-design-templates.md +6 -0
  390. package/corpus/templates/design/changelog/2026-07-10-screen-overview-frames-now-use-a-clearer-interact-action-wit.md +6 -0
  391. package/corpus/templates/design/changelog/2026-07-10-start-designs-from-source-linked-material-3-carbon-or-primer.md +6 -0
  392. package/corpus/templates/design/changelog/2026-07-10-tweaks-guidance.md +6 -0
  393. package/corpus/templates/design/changelog/2026-07-10-uploaded-fig-files-can-now-become-editable-design-screens-wi.md +6 -0
  394. package/corpus/templates/design/e2e/helpers.ts +3 -3
  395. package/corpus/templates/design/package.json +2 -0
  396. package/corpus/templates/design/scripts/qa-figma-stress-import.ts +96 -0
  397. package/corpus/templates/design/server/db/index.ts +10 -0
  398. package/corpus/templates/design/server/db/schema.ts +44 -0
  399. package/corpus/templates/design/server/handlers/import-design-file.ts +114 -80
  400. package/corpus/templates/design/server/handlers/index-design-system-with-builder.ts +19 -6
  401. package/corpus/templates/design/server/lib/design-export.ts +240 -3
  402. package/corpus/templates/design/server/lib/design-template-data.ts +89 -0
  403. package/corpus/templates/design/server/lib/design-to-figma-svg.ts +250 -26
  404. package/corpus/templates/design/server/lib/fig-file-decoder.ts +839 -0
  405. package/corpus/templates/design/server/lib/fig-file-import.ts +287 -0
  406. package/corpus/templates/design/server/lib/fig-file-limits.ts +3 -0
  407. package/corpus/templates/design/server/lib/fig-file-to-html.ts +2568 -0
  408. package/corpus/templates/design/server/lib/figma-design-context.ts +359 -0
  409. package/corpus/templates/design/server/lib/figma-node-import.ts +478 -22
  410. package/corpus/templates/design/server/lib/figma-node-to-html.ts +554 -29
  411. package/corpus/templates/design/server/lib/local-figma-qa-upload.ts +112 -0
  412. package/corpus/templates/design/server/lib/verify-write-grant.ts +16 -2
  413. package/corpus/templates/design/server/lib/visible-clipboard-html.ts +13 -3
  414. package/corpus/templates/design/server/plugins/agent-chat.ts +7 -0
  415. package/corpus/templates/design/server/plugins/db.ts +42 -0
  416. package/corpus/templates/design/server/plugins/local-figma-qa-upload.ts +7 -0
  417. package/corpus/templates/design/server/register-secrets.ts +8 -8
  418. package/corpus/templates/design/server/routes/api/qa-figma-import-assets/[assetId].get.ts +46 -0
  419. package/corpus/templates/design/server/source-workspace.ts +104 -12
  420. package/corpus/templates/design/shared/code-layer.ts +105 -28
  421. package/corpus/templates/design/shared/design-system-templates.ts +301 -0
  422. package/corpus/templates/design/shared/design-template-presets.ts +195 -0
  423. package/corpus/templates/design/shared/figma-url.ts +8 -0
  424. package/corpus/templates/design/shared/html-integrity.ts +306 -0
  425. package/corpus/templates/design/shared/interaction-states.ts +318 -23
  426. package/corpus/templates/design/shared/locked-layers.ts +151 -0
  427. package/corpus/templates/dispatch/.agents/skills/capture-learnings/SKILL.md +22 -9
  428. package/corpus/templates/dispatch/app/i18n/zh-TW.ts +48 -0
  429. package/corpus/templates/dispatch/app/i18n-data.ts +536 -1
  430. package/corpus/templates/dispatch/changelog/2026-07-10-connect-slack-workspaces-with-oauth-and-manage-channel-ident.md +6 -0
  431. package/corpus/templates/dispatch/changelog/2026-07-10-microsoft-teams-bots-and-discord-slash-commands-can-now-conn.md +6 -0
  432. package/corpus/templates/dispatch/changelog/2026-07-10-slack-and-telegram-now-route-uptime-monitor-requests-to-anal.md +6 -0
  433. package/corpus/templates/forms/.agents/skills/capture-learnings/SKILL.md +22 -9
  434. package/corpus/templates/forms/.agents/skills/form-publishing/SKILL.md +7 -0
  435. package/corpus/templates/forms/AGENTS.md +4 -0
  436. package/corpus/templates/forms/actions/create-form.ts +44 -7
  437. package/corpus/templates/forms/actions/lib/assert-publishable-form.ts +38 -0
  438. package/corpus/templates/forms/actions/patch-form-fields.ts +4 -0
  439. package/corpus/templates/forms/actions/update-form.ts +2 -36
  440. package/corpus/templates/forms/app/i18n/ar-SA.ts +3 -0
  441. package/corpus/templates/forms/app/i18n/de-DE.ts +3 -0
  442. package/corpus/templates/forms/app/i18n/en-US.ts +3 -0
  443. package/corpus/templates/forms/app/i18n/es-ES.ts +3 -0
  444. package/corpus/templates/forms/app/i18n/fr-FR.ts +3 -0
  445. package/corpus/templates/forms/app/i18n/hi-IN.ts +3 -0
  446. package/corpus/templates/forms/app/i18n/ja-JP.ts +3 -0
  447. package/corpus/templates/forms/app/i18n/ko-KR.ts +3 -0
  448. package/corpus/templates/forms/app/i18n/pt-BR.ts +3 -0
  449. package/corpus/templates/forms/app/i18n/zh-CN.ts +2 -0
  450. package/corpus/templates/forms/app/i18n/zh-TW.ts +3 -0
  451. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +17 -0
  452. package/corpus/templates/forms/changelog/2026-07-10-agent-created-published-forms-now-return-the-anonymous-publi.md +6 -0
  453. package/corpus/templates/forms/changelog/2026-07-10-published-forms-can-no-longer-be-edited-into-an-unusable-emp.md +6 -0
  454. package/corpus/templates/forms/server/handlers/submissions.ts +4 -3
  455. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -0
  456. package/corpus/templates/forms/shared/types.ts +5 -0
  457. package/corpus/templates/mail/.agents/skills/capture-learnings/SKILL.md +22 -9
  458. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +2 -2
  459. package/corpus/templates/mail/changelog/2026-07-10-mail-navigation-now-uses-a-clean-borderless-drawer.md +6 -0
  460. package/corpus/templates/plan/.agents/skills/capture-learnings/SKILL.md +22 -9
  461. package/corpus/templates/plan/app/lib/plan-local-bridge.ts +4 -1
  462. package/corpus/templates/plan/changelog/2026-07-10-local-plans-now-keep-valid-content-visible-when-one-generate.md +6 -0
  463. package/corpus/templates/plan/server/lib/local-plan-files.ts +4 -1
  464. package/corpus/templates/slides/.agents/skills/capture-learnings/SKILL.md +22 -9
  465. package/dist/a2a/artifact-response.d.ts.map +1 -1
  466. package/dist/a2a/artifact-response.js +88 -2
  467. package/dist/a2a/artifact-response.js.map +1 -1
  468. package/dist/a2a/client.d.ts +9 -0
  469. package/dist/a2a/client.d.ts.map +1 -1
  470. package/dist/a2a/client.js +1 -0
  471. package/dist/a2a/client.js.map +1 -1
  472. package/dist/a2a/handlers.d.ts.map +1 -1
  473. package/dist/a2a/handlers.js +24 -69
  474. package/dist/a2a/handlers.js.map +1 -1
  475. package/dist/a2a/types.d.ts +2 -0
  476. package/dist/a2a/types.d.ts.map +1 -1
  477. package/dist/a2a/types.js.map +1 -1
  478. package/dist/agent/durable-background.d.ts +11 -0
  479. package/dist/agent/durable-background.d.ts.map +1 -1
  480. package/dist/agent/durable-background.js +17 -0
  481. package/dist/agent/durable-background.js.map +1 -1
  482. package/dist/agent/production-agent.d.ts +5 -3
  483. package/dist/agent/production-agent.d.ts.map +1 -1
  484. package/dist/agent/production-agent.js +34 -8
  485. package/dist/agent/production-agent.js.map +1 -1
  486. package/dist/agent/run-manager.d.ts +44 -2
  487. package/dist/agent/run-manager.d.ts.map +1 -1
  488. package/dist/agent/run-manager.js +124 -18
  489. package/dist/agent/run-manager.js.map +1 -1
  490. package/dist/agent/run-store.d.ts +109 -0
  491. package/dist/agent/run-store.d.ts.map +1 -1
  492. package/dist/agent/run-store.js +274 -37
  493. package/dist/agent/run-store.js.map +1 -1
  494. package/dist/agent/types.d.ts +22 -0
  495. package/dist/agent/types.d.ts.map +1 -1
  496. package/dist/agent/types.js.map +1 -1
  497. package/dist/audit/record.d.ts.map +1 -1
  498. package/dist/audit/record.js +29 -0
  499. package/dist/audit/record.js.map +1 -1
  500. package/dist/audit/store.d.ts.map +1 -1
  501. package/dist/audit/store.js +62 -3
  502. package/dist/audit/store.js.map +1 -1
  503. package/dist/audit/types.d.ts +13 -0
  504. package/dist/audit/types.d.ts.map +1 -1
  505. package/dist/audit/types.js.map +1 -1
  506. package/dist/automation/index.d.ts +193 -0
  507. package/dist/automation/index.d.ts.map +1 -0
  508. package/dist/automation/index.js +464 -0
  509. package/dist/automation/index.js.map +1 -0
  510. package/dist/cli/create.d.ts.map +1 -1
  511. package/dist/cli/create.js +10 -3
  512. package/dist/cli/create.js.map +1 -1
  513. package/dist/cli/design-connect.d.ts.map +1 -1
  514. package/dist/cli/design-connect.js +492 -35
  515. package/dist/cli/design-connect.js.map +1 -1
  516. package/dist/client/AgentPanel.d.ts +7 -1
  517. package/dist/client/AgentPanel.d.ts.map +1 -1
  518. package/dist/client/AgentPanel.js +32 -8
  519. package/dist/client/AgentPanel.js.map +1 -1
  520. package/dist/client/AssistantChat.d.ts +15 -0
  521. package/dist/client/AssistantChat.d.ts.map +1 -1
  522. package/dist/client/AssistantChat.js +115 -47
  523. package/dist/client/AssistantChat.js.map +1 -1
  524. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  525. package/dist/client/MultiTabAssistantChat.js +1 -1
  526. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  527. package/dist/client/RunStuckBanner.d.ts +14 -1
  528. package/dist/client/RunStuckBanner.d.ts.map +1 -1
  529. package/dist/client/RunStuckBanner.js +35 -7
  530. package/dist/client/RunStuckBanner.js.map +1 -1
  531. package/dist/client/agent-chat-adapter.d.ts +4 -1
  532. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  533. package/dist/client/agent-chat-adapter.js +97 -16
  534. package/dist/client/agent-chat-adapter.js.map +1 -1
  535. package/dist/client/automation.d.ts +14 -0
  536. package/dist/client/automation.d.ts.map +1 -0
  537. package/dist/client/automation.js +9 -0
  538. package/dist/client/automation.js.map +1 -0
  539. package/dist/client/chat/run-recovery.d.ts +4 -0
  540. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  541. package/dist/client/chat/run-recovery.js +12 -10
  542. package/dist/client/chat/run-recovery.js.map +1 -1
  543. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  544. package/dist/client/composer/TiptapComposer.js +8 -0
  545. package/dist/client/composer/TiptapComposer.js.map +1 -1
  546. package/dist/client/index.d.ts +2 -0
  547. package/dist/client/index.d.ts.map +1 -1
  548. package/dist/client/index.js +2 -0
  549. package/dist/client/index.js.map +1 -1
  550. package/dist/client/integrations/api.d.ts +115 -0
  551. package/dist/client/integrations/api.d.ts.map +1 -0
  552. package/dist/client/integrations/api.js +123 -0
  553. package/dist/client/integrations/api.js.map +1 -0
  554. package/dist/client/integrations/index.d.ts +1 -0
  555. package/dist/client/integrations/index.d.ts.map +1 -1
  556. package/dist/client/integrations/index.js +1 -0
  557. package/dist/client/integrations/index.js.map +1 -1
  558. package/dist/client/transcription/use-live-transcription.d.ts.map +1 -1
  559. package/dist/client/transcription/use-live-transcription.js +59 -5
  560. package/dist/client/transcription/use-live-transcription.js.map +1 -1
  561. package/dist/client/use-run-stuck-detection.d.ts +16 -1
  562. package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
  563. package/dist/client/use-run-stuck-detection.js +23 -4
  564. package/dist/client/use-run-stuck-detection.js.map +1 -1
  565. package/dist/collab/awareness.d.ts +2 -2
  566. package/dist/collab/awareness.d.ts.map +1 -1
  567. package/dist/deploy/build.d.ts.map +1 -1
  568. package/dist/deploy/build.js +49 -4
  569. package/dist/deploy/build.js.map +1 -1
  570. package/dist/deploy/workspace-deploy.d.ts.map +1 -1
  571. package/dist/deploy/workspace-deploy.js +31 -2
  572. package/dist/deploy/workspace-deploy.js.map +1 -1
  573. package/dist/file-upload/types.d.ts +2 -0
  574. package/dist/file-upload/types.d.ts.map +1 -1
  575. package/dist/file-upload/types.js.map +1 -1
  576. package/dist/index.d.ts +2 -0
  577. package/dist/index.d.ts.map +1 -1
  578. package/dist/index.js +2 -0
  579. package/dist/index.js.map +1 -1
  580. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  581. package/dist/integrations/a2a-continuations-store.js +6 -4
  582. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  583. package/dist/integrations/adapters/discord.d.ts +3 -0
  584. package/dist/integrations/adapters/discord.d.ts.map +1 -0
  585. package/dist/integrations/adapters/discord.js +261 -0
  586. package/dist/integrations/adapters/discord.js.map +1 -0
  587. package/dist/integrations/adapters/index.d.ts +4 -0
  588. package/dist/integrations/adapters/index.d.ts.map +1 -0
  589. package/dist/integrations/adapters/index.js +26 -0
  590. package/dist/integrations/adapters/index.js.map +1 -0
  591. package/dist/integrations/adapters/microsoft-teams.d.ts +5 -0
  592. package/dist/integrations/adapters/microsoft-teams.d.ts.map +1 -0
  593. package/dist/integrations/adapters/microsoft-teams.js +295 -0
  594. package/dist/integrations/adapters/microsoft-teams.js.map +1 -0
  595. package/dist/integrations/adapters/slack.d.ts +8 -2
  596. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  597. package/dist/integrations/adapters/slack.js +736 -27
  598. package/dist/integrations/adapters/slack.js.map +1 -1
  599. package/dist/integrations/adapters/telegram.d.ts +0 -2
  600. package/dist/integrations/adapters/telegram.d.ts.map +1 -1
  601. package/dist/integrations/adapters/telegram.js +49 -15
  602. package/dist/integrations/adapters/telegram.js.map +1 -1
  603. package/dist/integrations/adapters/whatsapp.d.ts +0 -2
  604. package/dist/integrations/adapters/whatsapp.d.ts.map +1 -1
  605. package/dist/integrations/adapters/whatsapp.js +40 -17
  606. package/dist/integrations/adapters/whatsapp.js.map +1 -1
  607. package/dist/integrations/catalog.d.ts +75 -0
  608. package/dist/integrations/catalog.d.ts.map +1 -0
  609. package/dist/integrations/catalog.js +451 -0
  610. package/dist/integrations/catalog.js.map +1 -0
  611. package/dist/integrations/controls-store.d.ts +44 -0
  612. package/dist/integrations/controls-store.d.ts.map +1 -0
  613. package/dist/integrations/controls-store.js +129 -0
  614. package/dist/integrations/controls-store.js.map +1 -0
  615. package/dist/integrations/index.d.ts +13 -1
  616. package/dist/integrations/index.d.ts.map +1 -1
  617. package/dist/integrations/index.js +9 -0
  618. package/dist/integrations/index.js.map +1 -1
  619. package/dist/integrations/installations-store.d.ts +112 -0
  620. package/dist/integrations/installations-store.d.ts.map +1 -0
  621. package/dist/integrations/installations-store.js +493 -0
  622. package/dist/integrations/installations-store.js.map +1 -0
  623. package/dist/integrations/integration-memory.d.ts +24 -0
  624. package/dist/integrations/integration-memory.d.ts.map +1 -0
  625. package/dist/integrations/integration-memory.js +136 -0
  626. package/dist/integrations/integration-memory.js.map +1 -0
  627. package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
  628. package/dist/integrations/pending-tasks-retry-job.js +6 -5
  629. package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
  630. package/dist/integrations/pending-tasks-store.d.ts +17 -0
  631. package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
  632. package/dist/integrations/pending-tasks-store.js +71 -5
  633. package/dist/integrations/pending-tasks-store.js.map +1 -1
  634. package/dist/integrations/plugin.d.ts.map +1 -1
  635. package/dist/integrations/plugin.js +520 -35
  636. package/dist/integrations/plugin.js.map +1 -1
  637. package/dist/integrations/scope-store.d.ts +65 -0
  638. package/dist/integrations/scope-store.d.ts.map +1 -0
  639. package/dist/integrations/scope-store.js +361 -0
  640. package/dist/integrations/scope-store.js.map +1 -0
  641. package/dist/integrations/slack-manifest.d.ts +66 -0
  642. package/dist/integrations/slack-manifest.d.ts.map +1 -0
  643. package/dist/integrations/slack-manifest.js +70 -0
  644. package/dist/integrations/slack-manifest.js.map +1 -0
  645. package/dist/integrations/slack-oauth.d.ts +99 -0
  646. package/dist/integrations/slack-oauth.d.ts.map +1 -0
  647. package/dist/integrations/slack-oauth.js +226 -0
  648. package/dist/integrations/slack-oauth.js.map +1 -0
  649. package/dist/integrations/types.d.ts +144 -0
  650. package/dist/integrations/types.d.ts.map +1 -1
  651. package/dist/integrations/types.js +16 -1
  652. package/dist/integrations/types.js.map +1 -1
  653. package/dist/integrations/usage-budget-store.d.ts +84 -0
  654. package/dist/integrations/usage-budget-store.d.ts.map +1 -0
  655. package/dist/integrations/usage-budget-store.js +650 -0
  656. package/dist/integrations/usage-budget-store.js.map +1 -0
  657. package/dist/integrations/webhook-handler.d.ts +4 -0
  658. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  659. package/dist/integrations/webhook-handler.js +415 -47
  660. package/dist/integrations/webhook-handler.js.map +1 -1
  661. package/dist/jobs/scheduler.d.ts +6 -0
  662. package/dist/jobs/scheduler.d.ts.map +1 -1
  663. package/dist/jobs/scheduler.js +83 -4
  664. package/dist/jobs/scheduler.js.map +1 -1
  665. package/dist/jobs/tools.d.ts.map +1 -1
  666. package/dist/jobs/tools.js +43 -11
  667. package/dist/jobs/tools.js.map +1 -1
  668. package/dist/provider-api/index.d.ts +4 -4
  669. package/dist/provider-api/index.d.ts.map +1 -1
  670. package/dist/provider-api/index.js +72 -1
  671. package/dist/provider-api/index.js.map +1 -1
  672. package/dist/resources/handlers.d.ts.map +1 -1
  673. package/dist/resources/handlers.js +57 -15
  674. package/dist/resources/handlers.js.map +1 -1
  675. package/dist/resources/script-helpers.d.ts.map +1 -1
  676. package/dist/resources/script-helpers.js +34 -8
  677. package/dist/resources/script-helpers.js.map +1 -1
  678. package/dist/resources/store.d.ts +10 -0
  679. package/dist/resources/store.d.ts.map +1 -1
  680. package/dist/resources/store.js +54 -47
  681. package/dist/resources/store.js.map +1 -1
  682. package/dist/scripts/call-agent.d.ts.map +1 -1
  683. package/dist/scripts/call-agent.js +106 -1
  684. package/dist/scripts/call-agent.js.map +1 -1
  685. package/dist/server/agent-chat-plugin.d.ts +15 -4
  686. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  687. package/dist/server/agent-chat-plugin.js +248 -113
  688. package/dist/server/agent-chat-plugin.js.map +1 -1
  689. package/dist/server/auth.d.ts.map +1 -1
  690. package/dist/server/auth.js +10 -14
  691. package/dist/server/auth.js.map +1 -1
  692. package/dist/server/builder-design-systems.d.ts +3 -1
  693. package/dist/server/builder-design-systems.d.ts.map +1 -1
  694. package/dist/server/builder-design-systems.js +9 -2
  695. package/dist/server/builder-design-systems.js.map +1 -1
  696. package/dist/server/edge.d.ts +1 -1
  697. package/dist/server/edge.d.ts.map +1 -1
  698. package/dist/server/edge.js +1 -1
  699. package/dist/server/edge.js.map +1 -1
  700. package/dist/server/index.d.ts +2 -1
  701. package/dist/server/index.d.ts.map +1 -1
  702. package/dist/server/index.js +2 -1
  703. package/dist/server/index.js.map +1 -1
  704. package/dist/server/request-context.d.ts +18 -0
  705. package/dist/server/request-context.d.ts.map +1 -1
  706. package/dist/server/request-context.js.map +1 -1
  707. package/dist/server/social-og-image.d.ts.map +1 -1
  708. package/dist/server/social-og-image.js +3 -2
  709. package/dist/server/social-og-image.js.map +1 -1
  710. package/dist/server/transcribe-voice.d.ts +1 -1
  711. package/dist/styles/agent-native.css +62 -44
  712. package/dist/templates/default/.agents/skills/capture-learnings/SKILL.md +22 -9
  713. package/dist/templates/default/AGENTS.md +7 -3
  714. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +1 -1
  715. package/dist/templates/workspace-core/.agents/skills/capture-learnings/SKILL.md +22 -9
  716. package/dist/templates/workspace-core/.agents/skills/extension-points/SKILL.md +6 -3
  717. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +25 -0
  718. package/dist/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +15 -0
  719. package/dist/templates/workspace-core/AGENTS.md +3 -2
  720. package/dist/usage/store.d.ts +7 -0
  721. package/dist/usage/store.d.ts.map +1 -1
  722. package/dist/usage/store.js +24 -3
  723. package/dist/usage/store.js.map +1 -1
  724. package/docs/content/automation-connectors.mdx +80 -0
  725. package/docs/content/extensions.mdx +8 -0
  726. package/docs/content/locales/ar-SA/extensions.mdx +8 -0
  727. package/docs/content/locales/ar-SA/messaging.mdx +84 -15
  728. package/docs/content/locales/ar-SA/template-clips.mdx +1 -0
  729. package/docs/content/locales/ar-SA/template-design.mdx +1 -0
  730. package/docs/content/locales/de-DE/extensions.mdx +10 -0
  731. package/docs/content/locales/de-DE/messaging.mdx +92 -15
  732. package/docs/content/locales/de-DE/template-clips.mdx +1 -0
  733. package/docs/content/locales/de-DE/template-design.mdx +1 -0
  734. package/docs/content/locales/es-ES/extensions.mdx +10 -0
  735. package/docs/content/locales/es-ES/messaging.mdx +91 -15
  736. package/docs/content/locales/es-ES/template-clips.mdx +1 -0
  737. package/docs/content/locales/es-ES/template-design.mdx +1 -0
  738. package/docs/content/locales/fr-FR/extensions.mdx +10 -0
  739. package/docs/content/locales/fr-FR/messaging.mdx +91 -15
  740. package/docs/content/locales/fr-FR/template-clips.mdx +1 -0
  741. package/docs/content/locales/fr-FR/template-design.mdx +1 -0
  742. package/docs/content/locales/hi-IN/extensions.mdx +8 -0
  743. package/docs/content/locales/hi-IN/messaging.mdx +81 -15
  744. package/docs/content/locales/hi-IN/template-clips.mdx +1 -0
  745. package/docs/content/locales/hi-IN/template-design.mdx +1 -0
  746. package/docs/content/locales/ja-JP/extensions.mdx +9 -0
  747. package/docs/content/locales/ja-JP/messaging.mdx +81 -15
  748. package/docs/content/locales/ja-JP/template-clips.mdx +1 -0
  749. package/docs/content/locales/ja-JP/template-design.mdx +1 -0
  750. package/docs/content/locales/ko-KR/extensions.mdx +8 -0
  751. package/docs/content/locales/ko-KR/messaging.mdx +80 -15
  752. package/docs/content/locales/ko-KR/template-clips.mdx +1 -0
  753. package/docs/content/locales/ko-KR/template-design.mdx +1 -0
  754. package/docs/content/locales/pt-BR/extensions.mdx +9 -0
  755. package/docs/content/locales/pt-BR/messaging.mdx +88 -15
  756. package/docs/content/locales/pt-BR/template-clips.mdx +1 -0
  757. package/docs/content/locales/pt-BR/template-design.mdx +1 -0
  758. package/docs/content/locales/zh-CN/extensions.mdx +6 -0
  759. package/docs/content/locales/zh-CN/messaging.mdx +78 -15
  760. package/docs/content/locales/zh-CN/template-clips.mdx +1 -0
  761. package/docs/content/locales/zh-CN/template-design.mdx +1 -0
  762. package/docs/content/locales/zh-TW/extensions.mdx +6 -0
  763. package/docs/content/locales/zh-TW/messaging.mdx +78 -15
  764. package/docs/content/locales/zh-TW/template-clips.mdx +1 -0
  765. package/docs/content/locales/zh-TW/template-design.mdx +1 -0
  766. package/docs/content/messaging.mdx +227 -45
  767. package/docs/content/template-clips.mdx +3 -0
  768. package/docs/content/template-design.mdx +4 -0
  769. package/package.json +5 -1
  770. package/src/templates/default/.agents/skills/capture-learnings/SKILL.md +22 -9
  771. package/src/templates/default/AGENTS.md +7 -3
  772. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +1 -1
  773. package/src/templates/workspace-core/.agents/skills/capture-learnings/SKILL.md +22 -9
  774. package/src/templates/workspace-core/.agents/skills/extension-points/SKILL.md +6 -3
  775. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +25 -0
  776. package/src/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +15 -0
  777. package/src/templates/workspace-core/AGENTS.md +3 -2
@@ -44,8 +44,11 @@
44
44
  * | Vector networks / boolean ops / unsupported types | image fallback | never approximated structurally |
45
45
  *
46
46
  * ### Rotation caveat
47
- * The REST API's `rotation` field is in degrees (-180..180), and
48
- * `absoluteBoundingBox` is the *already-rotated* axis-aligned bounding box
47
+ * The REST API docs describe `rotation` as being in degrees, but the field
48
+ * is empirically returned in RADIANS (verified against known authored
49
+ * rotations via the Plugin API); this mapper converts it to degrees before
50
+ * use. `absoluteBoundingBox` is the *already-rotated* axis-aligned bounding
51
+ * box —
49
52
  * Figma does not expose the pre-rotation box directly. We reconstruct the
50
53
  * unrotated box by treating the AABB's center as invariant under rotation
51
54
  * (true for a shape rotated about its own center) and rotate the CSS element
@@ -100,6 +103,7 @@ export interface FigmaPaint {
100
103
  scaleMode?: "FILL" | "FIT" | "TILE" | "STRETCH";
101
104
  imageTransform?: [[number, number, number], [number, number, number]];
102
105
  filters?: FigmaImageFilter;
106
+ blendMode?: string;
103
107
  }
104
108
 
105
109
  export interface FigmaEffect {
@@ -128,6 +132,13 @@ export interface FigmaTypeStyle {
128
132
  textAlignHorizontal?: "LEFT" | "RIGHT" | "CENTER" | "JUSTIFIED";
129
133
  textAlignVertical?: "TOP" | "CENTER" | "BOTTOM";
130
134
  textAutoResize?: "NONE" | "WIDTH_AND_HEIGHT" | "HEIGHT" | "TRUNCATE";
135
+ paragraphSpacing?: number;
136
+ paragraphIndent?: number;
137
+ listSpacing?: number;
138
+ hangingPunctuation?: boolean;
139
+ hangingList?: boolean;
140
+ opentypeFlags?: Record<string, number>;
141
+ hyperlink?: unknown;
131
142
  fills?: FigmaPaint[];
132
143
  }
133
144
 
@@ -154,19 +165,42 @@ export interface FigmaNode {
154
165
  blendMode?: string;
155
166
  rotation?: number;
156
167
  absoluteBoundingBox?: FigmaBoundingBox;
168
+ /**
169
+ * The node's actual visual extent, INCLUDING stroke/effect overflow --
170
+ * e.g. an OUTSIDE-aligned stroke or a drop shadow makes this larger than
171
+ * `absoluteBoundingBox` (the purely geometric fill bounds). Figma's own
172
+ * `/v1/images` renders for a node are cropped to this box, not to
173
+ * `absoluteBoundingBox` -- sizing an image-fallback `<img>` using the
174
+ * geometric box instead squishes/crops the rendered PNG to the wrong
175
+ * aspect ratio whenever a fallback node has stroke/effect overflow.
176
+ */
177
+ absoluteRenderBounds?: FigmaBoundingBox;
157
178
  size?: { x: number; y: number };
158
179
  clipsContent?: boolean;
180
+ isMask?: boolean;
181
+ maskType?: "ALPHA" | "VECTOR" | "LUMINANCE";
182
+ arcData?: {
183
+ startingAngle?: number;
184
+ endingAngle?: number;
185
+ innerRadius?: number;
186
+ };
159
187
  characters?: string;
160
188
  style?: FigmaTypeStyle;
189
+ characterStyleOverrides?: number[];
190
+ styleOverrideTable?: Record<string, FigmaTypeStyle>;
191
+ lineTypes?: string[];
192
+ lineIndentations?: number[];
161
193
  fills?: FigmaPaint[];
162
194
  strokes?: FigmaPaint[];
163
195
  strokeWeight?: number;
164
196
  strokeAlign?: "INSIDE" | "OUTSIDE" | "CENTER";
165
197
  individualStrokeWeights?: FigmaIndividualStrokeWeights;
198
+ strokeDashes?: number[];
166
199
  cornerRadius?: number;
167
200
  rectangleCornerRadii?: [number, number, number, number];
168
201
  effects?: FigmaEffect[];
169
202
  layoutMode?: "NONE" | "HORIZONTAL" | "VERTICAL" | "GRID";
203
+ layoutPositioning?: "AUTO" | "ABSOLUTE";
170
204
  primaryAxisAlignItems?: "MIN" | "CENTER" | "MAX" | "SPACE_BETWEEN";
171
205
  counterAxisAlignItems?: "MIN" | "CENTER" | "MAX" | "BASELINE";
172
206
  layoutSizingHorizontal?: "FIXED" | "HUG" | "FILL";
@@ -178,6 +212,14 @@ export interface FigmaNode {
178
212
  paddingRight?: number;
179
213
  paddingTop?: number;
180
214
  paddingBottom?: number;
215
+ minWidth?: number | null;
216
+ maxWidth?: number | null;
217
+ minHeight?: number | null;
218
+ maxHeight?: number | null;
219
+ componentId?: string;
220
+ componentProperties?: Record<string, unknown>;
221
+ boundVariables?: Record<string, unknown>;
222
+ interactions?: unknown[];
181
223
  children?: FigmaNode[];
182
224
  }
183
225
 
@@ -236,6 +278,10 @@ const SUPPORTED_CONTAINER_TYPES = new Set([
236
278
  "SECTION",
237
279
  ]);
238
280
 
281
+ const MAX_FIGMA_NODE_COUNT = 75_000;
282
+ const MAX_FIGMA_NODE_DEPTH = 256;
283
+ const MAX_METADATA_ATTRIBUTE_CHARS = 16_384;
284
+
239
285
  // ---------------------------------------------------------------------------
240
286
  // Formatting helpers
241
287
  // ---------------------------------------------------------------------------
@@ -274,6 +320,35 @@ function escapeAttr(value: string): string {
274
320
  return escapeHtml(value);
275
321
  }
276
322
 
323
+ function metadataAttr(
324
+ name: string,
325
+ value: unknown,
326
+ node: FigmaNode,
327
+ tracker: FidelityTracker,
328
+ ): string {
329
+ if (value === undefined || value === null) return "";
330
+ let serialized: string;
331
+ try {
332
+ serialized = typeof value === "string" ? value : JSON.stringify(value);
333
+ } catch {
334
+ tracker.record(
335
+ node,
336
+ "approximated",
337
+ `${name} metadata could not be serialized and was omitted.`,
338
+ );
339
+ return "";
340
+ }
341
+ if (serialized.length > MAX_METADATA_ATTRIBUTE_CHARS) {
342
+ tracker.record(
343
+ node,
344
+ "approximated",
345
+ `${name} metadata exceeded ${MAX_METADATA_ATTRIBUTE_CHARS} characters and was omitted.`,
346
+ );
347
+ return "";
348
+ }
349
+ return ` ${name}="${escapeAttr(serialized)}"`;
350
+ }
351
+
277
352
  /**
278
353
  * Builds the CSS text for a `style="..."` attribute AND escapes it for HTML
279
354
  * attribute context. This matters because at least one style value we emit
@@ -386,16 +461,67 @@ export function gradientAngleDegrees(
386
461
  return ((angleDeg % 360) + 360) % 360;
387
462
  }
388
463
 
389
- function gradientStopsCss(paint: FigmaPaint): string {
464
+ function gradientStopsCss(
465
+ paint: FigmaPaint,
466
+ remapPosition?: (position: number) => number,
467
+ ): string {
390
468
  const stops = paint.gradientStops ?? [];
391
469
  return stops
392
470
  .map((stop) => {
393
471
  const color = colorToCss(stop.color, paint.opacity ?? 1) ?? "transparent";
394
- return `${color} ${round(stop.position * 100, 2)}%`;
472
+ const position = remapPosition
473
+ ? remapPosition(stop.position)
474
+ : stop.position;
475
+ return `${color} ${round(position * 100, 2)}%`;
395
476
  })
396
477
  .join(", ");
397
478
  }
398
479
 
480
+ /**
481
+ * CSS `linear-gradient(angle, ...)` always stretches its 0%/100% stops
482
+ * across the box's FULL diagonal extent at that angle (the CSS spec's
483
+ * "gradient line" always spans corner-to-corner) -- it has no way to say
484
+ * "start partway in, end partway in" the way Figma's actual gradient handles
485
+ * can (a designer can drag the start/end handles anywhere, including short
486
+ * of the shape's edges, or past them). Figma's own stop positions are
487
+ * fractions of the literal start-handle-to-end-handle distance, which only
488
+ * happens to coincide with the CSS full-box span when the handles are
489
+ * dragged exactly corner-to-corner -- a common case, but far from the only
490
+ * one, and the divergence gets worse the more the box's aspect ratio departs
491
+ * from square (rotated/skewed handles included, e.g. gradientTransform-authored
492
+ * paints). This projects each Figma stop's real pixel position onto the same
493
+ * angle CSS will use and re-expresses it as a percentage of the CSS line's
494
+ * length, so a partial/offset gradient renders at the same actual pixel
495
+ * positions Figma draws it at instead of silently stretching to fill the box.
496
+ */
497
+ function remapLinearStopPosition(
498
+ geometry: GradientGeometry,
499
+ box: { width: number; height: number },
500
+ angleDeg: number,
501
+ ): (position: number) => number {
502
+ const angleRad = (angleDeg * Math.PI) / 180;
503
+ const ux = Math.sin(angleRad);
504
+ const uy = -Math.cos(angleRad);
505
+ const lineLength = box.width * Math.abs(ux) + box.height * Math.abs(uy);
506
+ if (lineLength < 1e-6) return (position) => position;
507
+ const startPx = {
508
+ x: geometry.start.x * box.width,
509
+ y: geometry.start.y * box.height,
510
+ };
511
+ const endPx = {
512
+ x: geometry.end.x * box.width,
513
+ y: geometry.end.y * box.height,
514
+ };
515
+ const centerX = box.width / 2;
516
+ const centerY = box.height / 2;
517
+ return (position: number) => {
518
+ const pointX = startPx.x + position * (endPx.x - startPx.x);
519
+ const pointY = startPx.y + position * (endPx.y - startPx.y);
520
+ const projected = (pointX - centerX) * ux + (pointY - centerY) * uy;
521
+ return (projected + lineLength / 2) / lineLength;
522
+ };
523
+ }
524
+
399
525
  function vectorLength(
400
526
  from: { x: number; y: number },
401
527
  to: { x: number; y: number },
@@ -424,20 +550,40 @@ function paintToCssImage(
424
550
  switch (paint.type) {
425
551
  case "GRADIENT_LINEAR": {
426
552
  const angle = gradientAngleDegrees(paint, box);
553
+ const geometry = resolveGradientGeometry(paint);
554
+ // Re-express Figma's handle-relative stop positions as percentages of
555
+ // CSS's own full-box gradient line (see remapLinearStopPosition) so a
556
+ // gradient whose handles don't span exactly corner-to-corner still
557
+ // lands its color transitions at the same real pixel positions Figma
558
+ // draws them at, instead of being stretched to fill the whole box.
559
+ const linearStops =
560
+ angle !== null && geometry
561
+ ? gradientStopsCss(
562
+ paint,
563
+ remapLinearStopPosition(geometry, box, angle),
564
+ )
565
+ : stops;
427
566
  tracker.record(
428
567
  node,
429
568
  "exact",
430
- "Linear gradient angle derived from gradientHandlePositions.",
569
+ "Linear gradient angle and stop offsets derived from gradientHandlePositions.",
431
570
  );
432
- return `linear-gradient(${round(angle ?? 90, 2)}deg, ${stops})`;
571
+ return `linear-gradient(${round(angle ?? 90, 2)}deg, ${linearStops})`;
433
572
  }
434
573
  case "GRADIENT_RADIAL": {
435
574
  const geometry = resolveGradientGeometry(paint);
436
575
  if (!geometry) return `radial-gradient(${stops})`;
437
576
  const cx = round(geometry.start.x * 100, 2);
438
577
  const cy = round(geometry.start.y * 100, 2);
439
- const radiusX = vectorLength(geometry.start, geometry.width, box);
440
- const radiusY = vectorLength(geometry.start, geometry.end, box);
578
+ // Figma's handle[1] ("end") is the radius vector along the gradient's
579
+ // own primary axis; handle[2] ("width") is the perpendicular radius.
580
+ // For an axis-aligned box those map directly to the ellipse's
581
+ // horizontal/vertical radii -- swapping them (as a prior version of
582
+ // this code did) silently rotates the ellipse 90 degrees, which is
583
+ // invisible for a square box but produces a badly wrong bowtie-shaped
584
+ // gradient for any non-square rectangle (the common case).
585
+ const radiusX = vectorLength(geometry.start, geometry.end, box);
586
+ const radiusY = vectorLength(geometry.start, geometry.width, box);
441
587
  tracker.record(
442
588
  node,
443
589
  "approximated",
@@ -461,11 +607,14 @@ function paintToCssImage(
461
607
  const geometry = resolveGradientGeometry(paint);
462
608
  const cx = geometry ? round(geometry.start.x * 100, 2) : 50;
463
609
  const cy = geometry ? round(geometry.start.y * 100, 2) : 50;
610
+ // Same handle-to-axis mapping fix as GRADIENT_RADIAL above: handle[1]
611
+ // ("end") is the primary-axis radius, handle[2] ("width") the
612
+ // perpendicular one.
464
613
  const radiusX = geometry
465
- ? vectorLength(geometry.start, geometry.width, box)
614
+ ? vectorLength(geometry.start, geometry.end, box)
466
615
  : box.width / 2;
467
616
  const radiusY = geometry
468
- ? vectorLength(geometry.start, geometry.end, box)
617
+ ? vectorLength(geometry.start, geometry.width, box)
469
618
  : box.height / 2;
470
619
  tracker.record(
471
620
  node,
@@ -1061,11 +1210,101 @@ function needsImageFallback(
1061
1210
  ): boolean {
1062
1211
  if (options.forceImageFallbackNodeIds?.has(node.id)) return true;
1063
1212
  if (UNSUPPORTED_STRUCTURAL_TYPES.has(node.type)) return true;
1213
+ // A Figma mask affects its following siblings, not just the mask node. CSS
1214
+ // cannot reproduce that sibling-range operation on an arbitrary DOM tree,
1215
+ // so render the smallest containing subtree rather than importing a visibly
1216
+ // wrong unmasked composition. A mask imported as the root is also rendered.
1217
+ if (node.isMask || node.children?.some((child) => child.isMask)) return true;
1218
+ // A CSS div with an outline is not a Figma line. Partial/ring ellipses also
1219
+ // need real path geometry, which this structural mapper intentionally does
1220
+ // not request because geometry=paths makes ordinary REST payloads enormous.
1221
+ if (node.type === "LINE") return true;
1222
+ if (node.type === "ELLIPSE" && node.arcData) {
1223
+ const start = node.arcData.startingAngle ?? 0;
1224
+ const end = node.arcData.endingAngle ?? Math.PI * 2;
1225
+ const span = Math.abs(end - start);
1226
+ const isFullCircle =
1227
+ Math.abs(span - Math.PI * 2) < 1e-4 &&
1228
+ Math.abs(node.arcData.innerRadius ?? 0) < 1e-4;
1229
+ if (!isFullCircle) return true;
1230
+ }
1231
+ const visibleStrokes = (node.strokes ?? []).filter(
1232
+ (stroke) => stroke.visible !== false,
1233
+ );
1234
+ if (
1235
+ visibleStrokes.length > 1 ||
1236
+ visibleStrokes.some((stroke) => stroke.type !== "SOLID") ||
1237
+ (node.strokeDashes?.length ?? 0) > 0
1238
+ ) {
1239
+ return true;
1240
+ }
1241
+ const visibleFills = (node.fills ?? []).filter(
1242
+ (fill) => fill.visible !== false,
1243
+ );
1244
+ if (
1245
+ visibleFills.some(
1246
+ (fill) =>
1247
+ fill.type === "VIDEO" ||
1248
+ fill.type === "EMOJI" ||
1249
+ (fill.blendMode &&
1250
+ fill.blendMode !== "NORMAL" &&
1251
+ fill.blendMode !== "PASS_THROUGH") ||
1252
+ (fill.type === "IMAGE" &&
1253
+ ((fill.imageTransform &&
1254
+ (Math.abs(fill.imageTransform[0][1]) >= 1e-6 ||
1255
+ Math.abs(fill.imageTransform[1][0]) >= 1e-6)) ||
1256
+ Object.values(fill.filters ?? {}).some(
1257
+ (value) => typeof value === "number" && Math.abs(value) > 1e-6,
1258
+ ))),
1259
+ ) ||
1260
+ (node.type === "TEXT" && visibleFills.some((fill) => fill.type !== "SOLID"))
1261
+ ) {
1262
+ return true;
1263
+ }
1264
+ if (node.type === "TEXT") {
1265
+ const styles = [
1266
+ node.style,
1267
+ ...Object.values(node.styleOverrideTable ?? {}),
1268
+ ].filter((style): style is FigmaTypeStyle => Boolean(style));
1269
+ const hasAdvancedTypography = styles.some(
1270
+ (style) =>
1271
+ Math.abs(style.paragraphSpacing ?? 0) > 1e-6 ||
1272
+ Math.abs(style.paragraphIndent ?? 0) > 1e-6 ||
1273
+ Math.abs(style.listSpacing ?? 0) > 1e-6 ||
1274
+ style.hangingPunctuation === true ||
1275
+ style.hangingList === true ||
1276
+ style.hyperlink !== undefined ||
1277
+ Object.values(style.opentypeFlags ?? {}).some((value) => value !== 0),
1278
+ );
1279
+ if (
1280
+ hasAdvancedTypography ||
1281
+ // Figma's REST API always returns one `lineTypes` entry per line —
1282
+ // ordinary non-list text comes back as `["NONE", "NONE", ...]`, not an
1283
+ // empty array. Checking `.length > 0` alone treats every multi-line
1284
+ // text node in existence as an unsupported list and routes it to an
1285
+ // image fallback; only a line whose type is actually "ORDERED" or
1286
+ // "UNORDERED" means the text uses real list formatting.
1287
+ (node.lineTypes?.some((type) => type !== "NONE") ?? false) ||
1288
+ (node.lineIndentations?.some((value) => value !== 0) ?? false)
1289
+ ) {
1290
+ return true;
1291
+ }
1292
+ }
1293
+ if (
1294
+ (node.effects ?? []).some(
1295
+ (effect) =>
1296
+ effect.visible !== false &&
1297
+ effect.blendMode &&
1298
+ effect.blendMode !== "NORMAL" &&
1299
+ effect.blendMode !== "PASS_THROUGH",
1300
+ )
1301
+ ) {
1302
+ return true;
1303
+ }
1064
1304
  if (
1065
1305
  !SUPPORTED_CONTAINER_TYPES.has(node.type) &&
1066
1306
  node.type !== "RECTANGLE" &&
1067
1307
  node.type !== "ELLIPSE" &&
1068
- node.type !== "LINE" &&
1069
1308
  node.type !== "TEXT"
1070
1309
  ) {
1071
1310
  return true;
@@ -1073,6 +1312,34 @@ function needsImageFallback(
1073
1312
  return false;
1074
1313
  }
1075
1314
 
1315
+ /** Fail clearly before recursive rendering can overflow or lock the worker. */
1316
+ export function assertFigmaNodeTreeComplexity(node: FigmaNode): void {
1317
+ const stack: Array<{ node: FigmaNode; depth: number }> = [{ node, depth: 1 }];
1318
+ const ancestors = new WeakSet<object>();
1319
+ let count = 0;
1320
+ while (stack.length > 0) {
1321
+ const current = stack.pop()!;
1322
+ count += 1;
1323
+ if (count > MAX_FIGMA_NODE_COUNT) {
1324
+ throw new Error(
1325
+ `Figma node tree is too large (max ${MAX_FIGMA_NODE_COUNT.toLocaleString("en-US")} nodes). Import a smaller frame or selection.`,
1326
+ );
1327
+ }
1328
+ if (current.depth > MAX_FIGMA_NODE_DEPTH) {
1329
+ throw new Error(
1330
+ `Figma node tree is nested too deeply (max ${MAX_FIGMA_NODE_DEPTH} levels). Import a smaller frame or selection.`,
1331
+ );
1332
+ }
1333
+ if (ancestors.has(current.node)) {
1334
+ throw new Error("Figma node tree contains a cyclic child reference.");
1335
+ }
1336
+ ancestors.add(current.node);
1337
+ for (const child of current.node.children ?? []) {
1338
+ stack.push({ node: child, depth: current.depth + 1 });
1339
+ }
1340
+ }
1341
+ }
1342
+
1076
1343
  /**
1077
1344
  * Walk a node tree and return the ids of every subtree that will render as a
1078
1345
  * PNG image fallback (vector networks, boolean ops, and any node type this
@@ -1084,9 +1351,10 @@ export function collectFallbackNodeIds(
1084
1351
  node: FigmaNode,
1085
1352
  options: MapFigmaNodeOptions = {},
1086
1353
  ): string[] {
1354
+ assertFigmaNodeTreeComplexity(node);
1087
1355
  const ids: string[] = [];
1088
1356
  const visit = (current: FigmaNode) => {
1089
- if (current.visible === false) return;
1357
+ if (current.visible === false || current.opacity === 0) return;
1090
1358
  if (needsImageFallback(current, options)) {
1091
1359
  ids.push(current.id);
1092
1360
  return; // Don't recurse into a subtree that's rendered as one image.
@@ -1102,7 +1370,11 @@ export function collectFallbackNodeIds(
1102
1370
  * (on fills or strokes) so the caller can resolve them to URLs via
1103
1371
  * `GET /v1/files/:fileKey/images` before mapping.
1104
1372
  */
1105
- export function collectImageFillRefs(node: FigmaNode): string[] {
1373
+ export function collectImageFillRefs(
1374
+ node: FigmaNode,
1375
+ options: MapFigmaNodeOptions = {},
1376
+ ): string[] {
1377
+ assertFigmaNodeTreeComplexity(node);
1106
1378
  const refs = new Set<string>();
1107
1379
  const visitPaints = (paints: FigmaPaint[] | undefined) => {
1108
1380
  for (const paint of paints ?? []) {
@@ -1110,6 +1382,8 @@ export function collectImageFillRefs(node: FigmaNode): string[] {
1110
1382
  }
1111
1383
  };
1112
1384
  const visit = (current: FigmaNode) => {
1385
+ if (current.visible === false || current.opacity === 0) return;
1386
+ if (needsImageFallback(current, options)) return;
1113
1387
  visitPaints(current.fills);
1114
1388
  visitPaints(current.strokes);
1115
1389
  for (const child of current.children ?? []) visit(child);
@@ -1118,6 +1392,119 @@ export function collectImageFillRefs(node: FigmaNode): string[] {
1118
1392
  return [...refs];
1119
1393
  }
1120
1394
 
1395
+ export interface FigmaFontUsage {
1396
+ family: string;
1397
+ weight: number;
1398
+ italic: boolean;
1399
+ }
1400
+
1401
+ function recordFontUsage(
1402
+ style: FigmaTypeStyle | undefined,
1403
+ usage: Map<string, FigmaFontUsage>,
1404
+ ): void {
1405
+ if (!style?.fontFamily) return;
1406
+ const weight = typeof style.fontWeight === "number" ? style.fontWeight : 400;
1407
+ const italic = Boolean(style.italic);
1408
+ const key = `${style.fontFamily}|${weight}|${italic ? 1 : 0}`;
1409
+ if (!usage.has(key))
1410
+ usage.set(key, { family: style.fontFamily, weight, italic });
1411
+ }
1412
+
1413
+ /**
1414
+ * Walk a node tree and return every distinct (font family, weight, italic)
1415
+ * combination used by TEXT nodes -- including per-run character style
1416
+ * overrides -- so the caller can request the actual web font (e.g. from
1417
+ * Google Fonts) before the imported HTML is saved. Without this, an imported
1418
+ * screen's CSS correctly names the intended font-family, but the browser has
1419
+ * no way to load it and silently falls back to a generic sans-serif with
1420
+ * different glyph advance widths -- individually invisible per character but
1421
+ * compounding into a growing horizontal drift across any wrapped or
1422
+ * multi-word line, worst on text-dense imports.
1423
+ */
1424
+ export function collectFontUsage(node: FigmaNode): FigmaFontUsage[] {
1425
+ assertFigmaNodeTreeComplexity(node);
1426
+ const usage = new Map<string, FigmaFontUsage>();
1427
+ const visit = (current: FigmaNode) => {
1428
+ if (current.visible === false || current.opacity === 0) return;
1429
+ if (current.type === "TEXT") {
1430
+ recordFontUsage(current.style, usage);
1431
+ for (const style of Object.values(current.styleOverrideTable ?? {})) {
1432
+ recordFontUsage(style, usage);
1433
+ }
1434
+ }
1435
+ for (const child of current.children ?? []) visit(child);
1436
+ };
1437
+ visit(node);
1438
+ return [...usage.values()];
1439
+ }
1440
+
1441
+ function textOverrideCss(
1442
+ style: FigmaTypeStyle | undefined,
1443
+ ): Record<string, string | undefined> {
1444
+ const solidFill = [...(style?.fills ?? [])]
1445
+ .reverse()
1446
+ .find((fill) => fill.visible !== false && fill.type === "SOLID");
1447
+ return {
1448
+ "font-family": style?.fontFamily
1449
+ ? `"${style.fontFamily.replace(/"/g, "")}", sans-serif`
1450
+ : undefined,
1451
+ "font-size": px(style?.fontSize),
1452
+ "font-weight":
1453
+ typeof style?.fontWeight === "number"
1454
+ ? String(style.fontWeight)
1455
+ : undefined,
1456
+ "font-style": style?.italic ? "italic" : undefined,
1457
+ "line-height": style ? resolveLineHeight(style) : undefined,
1458
+ "letter-spacing":
1459
+ typeof style?.letterSpacing === "number"
1460
+ ? px(style.letterSpacing)
1461
+ : undefined,
1462
+ "text-transform": textTransformCss(style?.textCase),
1463
+ "text-decoration": textDecorationCss(style?.textDecoration),
1464
+ color: solidFill
1465
+ ? (colorToCss(solidFill.color, solidFill.opacity ?? 1) ?? undefined)
1466
+ : undefined,
1467
+ };
1468
+ }
1469
+
1470
+ /** Render contiguous Figma character-style override runs as inline spans. */
1471
+ function buildMixedTextHtml(
1472
+ node: FigmaNode,
1473
+ characters: string,
1474
+ tracker: FidelityTracker,
1475
+ ): string {
1476
+ const overrideIds = node.characterStyleOverrides ?? [];
1477
+ const table = node.styleOverrideTable ?? {};
1478
+ if (
1479
+ characters.length === 0 ||
1480
+ !overrideIds.some((id) => id !== 0 && table[String(id)])
1481
+ ) {
1482
+ return escapeHtml(characters);
1483
+ }
1484
+
1485
+ const runs: Array<{ id: number; text: string }> = [];
1486
+ for (let index = 0; index < characters.length; index += 1) {
1487
+ const id = overrideIds[index] ?? 0;
1488
+ const previous = runs[runs.length - 1];
1489
+ if (previous?.id === id) previous.text += characters[index] ?? "";
1490
+ else runs.push({ id, text: characters[index] ?? "" });
1491
+ }
1492
+
1493
+ tracker.record(
1494
+ node,
1495
+ "exact",
1496
+ "Mixed character style overrides were preserved as inline text runs.",
1497
+ );
1498
+ return runs
1499
+ .map((run) => {
1500
+ if (run.id === 0) return escapeHtml(run.text);
1501
+ const style = table[String(run.id)];
1502
+ if (!style) return escapeHtml(run.text);
1503
+ return `<span style="${styleAttr(textOverrideCss(style))}">${escapeHtml(run.text)}</span>`;
1504
+ })
1505
+ .join("");
1506
+ }
1507
+
1121
1508
  // ---------------------------------------------------------------------------
1122
1509
  // Main mapper
1123
1510
  // ---------------------------------------------------------------------------
@@ -1136,6 +1523,72 @@ function frameRelativeBox(
1136
1523
  };
1137
1524
  }
1138
1525
 
1526
+ /**
1527
+ * Figma's `absoluteBoundingBox` for a rotated node is the axis-aligned
1528
+ * bounding box of the ALREADY-ROTATED shape, not the shape's own (pre-
1529
+ * rotation) width/height -- e.g. a 120x80 rectangle rotated 15 degrees comes
1530
+ * back with an ~136.6x108.3 bounding box. Applying a CSS `rotate()` on TOP
1531
+ * of a div already sized to that expanded AABB rotates an oversized box,
1532
+ * producing a visibly wrong (too-large, wrong-aspect-ratio) rotated shape.
1533
+ * This inverts the AABB formula (`W' = W*|cos| + H*|sin|`,
1534
+ * `H' = W*|sin| + H*|cos|`) to recover the true pre-rotation width/height,
1535
+ * then re-centers the (smaller) box at the same center point the AABB had --
1536
+ * matching this module's existing "rotate about the AABB center" pivot
1537
+ * assumption, so the CSS `rotate()` reproduces the original box exactly.
1538
+ */
1539
+ function unrotateBox(
1540
+ box: { left: number; top: number; width: number; height: number },
1541
+ rotationDeg: number,
1542
+ ): { left: number; top: number; width: number; height: number } {
1543
+ const theta = (rotationDeg * Math.PI) / 180;
1544
+ const c = Math.abs(Math.cos(theta));
1545
+ const s = Math.abs(Math.sin(theta));
1546
+ const det = c * c - s * s;
1547
+ // Near +-45/+-135 degrees the AABB<->true-size system is near-singular
1548
+ // (many different true sizes produce almost the same AABB); fall back to
1549
+ // the AABB dimensions rather than dividing by ~zero and producing a huge
1550
+ // or negative "true" size.
1551
+ if (Math.abs(det) < 0.05) return box;
1552
+ const trueWidth = (c * box.width - s * box.height) / det;
1553
+ const trueHeight = (c * box.height - s * box.width) / det;
1554
+ if (
1555
+ !Number.isFinite(trueWidth) ||
1556
+ !Number.isFinite(trueHeight) ||
1557
+ trueWidth <= 0 ||
1558
+ trueHeight <= 0
1559
+ ) {
1560
+ return box;
1561
+ }
1562
+ const centerX = box.left + box.width / 2;
1563
+ const centerY = box.top + box.height / 2;
1564
+ return {
1565
+ left: centerX - trueWidth / 2,
1566
+ top: centerY - trueHeight / 2,
1567
+ width: trueWidth,
1568
+ height: trueHeight,
1569
+ };
1570
+ }
1571
+
1572
+ /**
1573
+ * Same as `frameRelativeBox` but sized/positioned from `absoluteRenderBounds`
1574
+ * (falling back to `absoluteBoundingBox` when Figma didn't return render
1575
+ * bounds). Used only for image-fallback `<img>` geometry -- see the
1576
+ * `absoluteRenderBounds` field doc for why the geometric box is wrong there.
1577
+ */
1578
+ function frameRelativeRenderBox(
1579
+ node: FigmaNode,
1580
+ parentBox: FigmaBoundingBox | null,
1581
+ ): { left: number; top: number; width: number; height: number } {
1582
+ const box = node.absoluteRenderBounds ?? node.absoluteBoundingBox;
1583
+ if (!box) return { left: 0, top: 0, width: 0, height: 0 };
1584
+ return {
1585
+ left: box.x - (parentBox?.x ?? box.x),
1586
+ top: box.y - (parentBox?.y ?? box.y),
1587
+ width: box.width,
1588
+ height: box.height,
1589
+ };
1590
+ }
1591
+
1139
1592
  function buildNode(
1140
1593
  node: FigmaNode,
1141
1594
  parentBox: FigmaBoundingBox | null,
@@ -1145,14 +1598,72 @@ function buildNode(
1145
1598
  isRoot: boolean,
1146
1599
  ): string {
1147
1600
  const parentHasAutoLayout = parentLayoutMode !== "NONE";
1148
- if (node.visible === false) return "";
1149
-
1150
- const box = frameRelativeBox(node, parentBox);
1601
+ if (node.visible === false || node.opacity === 0) return "";
1602
+
1603
+ let box = frameRelativeBox(node, parentBox);
1604
+ // The Figma REST API's file-node-types docs describe `rotation` as
1605
+ // "in degrees", but empirically (verified against known authored values
1606
+ // via the Plugin API -- e.g. an authored 15deg/20deg rotation comes back
1607
+ // as 0.2617993.../0.3490658... here) the REST API actually returns
1608
+ // RADIANS. Treating that value as degrees silently shrinks every rotation
1609
+ // by a factor of ~57 (pi/180), rendering rotated content as visually
1610
+ // unrotated. Convert to degrees before using it anywhere below.
1611
+ const rotationDeg =
1612
+ typeof node.rotation === "number"
1613
+ ? (node.rotation * 180) / Math.PI
1614
+ : undefined;
1615
+ const rotation =
1616
+ rotationDeg !== undefined && Math.abs(rotationDeg) > 0.001
1617
+ ? rotationDeg
1618
+ : undefined;
1619
+ if (rotation !== undefined) {
1620
+ // `box` (from absoluteBoundingBox) is the rotated shape's AABB; recover
1621
+ // the true pre-rotation width/height/position so fills/effects/strokes
1622
+ // below -- and the CSS `rotate()` applied later -- operate on the
1623
+ // correct box instead of an oversized one. See `unrotateBox`.
1624
+ box = unrotateBox(box, rotation);
1625
+ }
1151
1626
  const nameAttr = node.name
1152
1627
  ? ` data-agent-native-layer-name="${escapeAttr(node.name)}"`
1153
1628
  : "";
1154
1629
  const idAttr = ` data-figma-node-id="${escapeAttr(node.id)}"`;
1155
1630
  const typeAttr = ` data-figma-node-type="${escapeAttr(node.type)}"`;
1631
+ const semanticAttrs =
1632
+ metadataAttr("data-figma-component-id", node.componentId, node, tracker) +
1633
+ metadataAttr(
1634
+ "data-figma-component-properties",
1635
+ node.componentProperties,
1636
+ node,
1637
+ tracker,
1638
+ ) +
1639
+ metadataAttr(
1640
+ "data-figma-bound-variables",
1641
+ node.boundVariables,
1642
+ node,
1643
+ tracker,
1644
+ ) +
1645
+ metadataAttr("data-figma-interactions", node.interactions, node, tracker);
1646
+ if (node.componentId || node.componentProperties) {
1647
+ tracker.record(
1648
+ node,
1649
+ "approximated",
1650
+ "Figma component/instance provenance was preserved as metadata, but the imported HTML is not linked to the original Figma component master.",
1651
+ );
1652
+ }
1653
+ if (node.boundVariables && Object.keys(node.boundVariables).length > 0) {
1654
+ tracker.record(
1655
+ node,
1656
+ "approximated",
1657
+ "Figma variable bindings were preserved as metadata; resolved visual values are imported, but bindings are not live Design tokens.",
1658
+ );
1659
+ }
1660
+ if (node.interactions && node.interactions.length > 0) {
1661
+ tracker.record(
1662
+ node,
1663
+ "approximated",
1664
+ "Prototype interactions were preserved as inert metadata and do not execute or navigate inside the editor preview.",
1665
+ );
1666
+ }
1156
1667
 
1157
1668
  if (needsImageFallback(node, options)) {
1158
1669
  const imageUrl = options.fallbackImageUrls?.[node.id];
@@ -1169,18 +1680,25 @@ function buildNode(
1169
1680
  "image-fallback",
1170
1681
  `Node type "${node.type}" cannot be reproduced structurally (vector network / boolean op / unsupported type); rendered as an exact PNG (scale=2) instead of an approximated structural guess.`,
1171
1682
  );
1683
+ const isFlowChild =
1684
+ !isRoot && parentHasAutoLayout && node.layoutPositioning !== "ABSOLUTE";
1685
+ // Use render bounds (not the geometric box) so a fallback PNG whose
1686
+ // stroke/effects overflow the node's own bounding box (e.g. an
1687
+ // OUTSIDE-aligned stroke) is placed at its natural size instead of being
1688
+ // squished/cropped into the smaller geometric box.
1689
+ const renderBox = frameRelativeRenderBox(node, parentBox);
1172
1690
  const styles: Record<string, string | undefined> = {
1173
- position: isRoot ? "relative" : "absolute",
1174
- left: isRoot ? undefined : px(box.left),
1175
- top: isRoot ? undefined : px(box.top),
1176
- width: px(box.width),
1177
- height: px(box.height),
1691
+ position: isRoot || isFlowChild ? "relative" : "absolute",
1692
+ left: isRoot || isFlowChild ? undefined : px(renderBox.left),
1693
+ top: isRoot || isFlowChild ? undefined : px(renderBox.top),
1694
+ width: px(renderBox.width),
1695
+ height: px(renderBox.height),
1178
1696
  opacity:
1179
1697
  typeof node.opacity === "number" && node.opacity !== 1
1180
1698
  ? String(round(node.opacity, 4))
1181
1699
  : undefined,
1182
1700
  };
1183
- return `<img${idAttr}${typeAttr}${nameAttr} src="${escapeAttr(imageUrl)}" alt="${escapeAttr(node.name ?? "")}" style="${styleAttr(styles)}" />`;
1701
+ return `<img${idAttr}${typeAttr}${nameAttr}${semanticAttrs} src="${escapeAttr(imageUrl)}" alt="${escapeAttr(node.name ?? "")}" style="${styleAttr(styles)}" />`;
1184
1702
  }
1185
1703
 
1186
1704
  const isTextNode = node.type === "TEXT";
@@ -1203,10 +1721,6 @@ function buildNode(
1203
1721
  const boxShadowParts = [...effects.boxShadowLayers];
1204
1722
  if (strokeResult.insetShadow) boxShadowParts.push(strokeResult.insetShadow);
1205
1723
 
1206
- const rotation =
1207
- typeof node.rotation === "number" && Math.abs(node.rotation) > 0.001
1208
- ? node.rotation
1209
- : undefined;
1210
1724
  if (rotation !== undefined) {
1211
1725
  tracker.record(
1212
1726
  node,
@@ -1223,7 +1737,8 @@ function buildNode(
1223
1737
  // container, in which case it's a normal flex item (relative, no left/top)
1224
1738
  // -- this mirrors Figma's own rule that auto-layout children give up
1225
1739
  // manual x/y in favor of flex flow.
1226
- const isFlexChild = !isRoot && parentHasAutoLayout;
1740
+ const isFlexChild =
1741
+ !isRoot && parentHasAutoLayout && node.layoutPositioning !== "ABSOLUTE";
1227
1742
 
1228
1743
  const baseStyles: Record<string, string | undefined> = {
1229
1744
  position: isRoot ? "relative" : isFlexChild ? "relative" : "absolute",
@@ -1252,6 +1767,10 @@ function buildNode(
1252
1767
  transform:
1253
1768
  rotation !== undefined ? `rotate(${round(-rotation, 3)}deg)` : undefined,
1254
1769
  "transform-origin": rotation !== undefined ? "center" : undefined,
1770
+ "min-width": px(node.minWidth ?? undefined),
1771
+ "max-width": px(node.maxWidth ?? undefined),
1772
+ "min-height": px(node.minHeight ?? undefined),
1773
+ "max-height": px(node.maxHeight ?? undefined),
1255
1774
  ...autoLayoutStyles,
1256
1775
  ...strokeResult.styles,
1257
1776
  ...childSizingStyles,
@@ -1280,6 +1799,10 @@ function buildNode(
1280
1799
  baseStyles["white-space"] = "nowrap";
1281
1800
  baseStyles.overflow = "hidden";
1282
1801
  baseStyles["text-overflow"] = "ellipsis";
1802
+ } else {
1803
+ // Figma preserves explicit newlines and repeated spaces. Normal HTML
1804
+ // whitespace collapsing changes both wrapping and measured geometry.
1805
+ baseStyles["white-space"] = "pre-wrap";
1283
1806
  }
1284
1807
  baseStyles.display = "flex";
1285
1808
  baseStyles["flex-direction"] = "column";
@@ -1289,7 +1812,8 @@ function buildNode(
1289
1812
  tracker.record(node, "exact", "Text styling mapped from TypeStyle fields.");
1290
1813
 
1291
1814
  const characters = node.characters ?? "";
1292
- return `<div${idAttr}${typeAttr}${nameAttr} style="${styleAttr(baseStyles)}">${escapeHtml(characters)}</div>`;
1815
+ const textHtml = buildMixedTextHtml(node, characters, tracker);
1816
+ return `<div${idAttr}${typeAttr}${nameAttr}${semanticAttrs} style="${styleAttr(baseStyles)}"><span>${textHtml}</span></div>`;
1293
1817
  }
1294
1818
 
1295
1819
  tracker.record(
@@ -1316,7 +1840,7 @@ function buildNode(
1316
1840
  .filter(Boolean)
1317
1841
  .join("\n");
1318
1842
 
1319
- return `<div${idAttr}${typeAttr}${nameAttr} style="${styleAttr(baseStyles)}">\n${childrenHtml}\n</div>`;
1843
+ return `<div${idAttr}${typeAttr}${nameAttr}${semanticAttrs} style="${styleAttr(baseStyles)}">\n${childrenHtml}\n</div>`;
1320
1844
  }
1321
1845
 
1322
1846
  /**
@@ -1328,6 +1852,7 @@ export function mapFigmaNodeToHtml(
1328
1852
  node: FigmaNode,
1329
1853
  options: MapFigmaNodeOptions = {},
1330
1854
  ): MapFigmaNodeResult {
1855
+ assertFigmaNodeTreeComplexity(node);
1331
1856
  const tracker = new FidelityTracker();
1332
1857
  const html = buildNode(node, null, "NONE", options, tracker, true);
1333
1858
  return { html, fidelity: tracker.build() };