@agent-native/core 0.92.9 → 0.92.11

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 (345) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +29 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +181 -15
  5. package/corpus/core/src/agent/run-manager.ts +31 -4
  6. package/corpus/core/src/agent/run-store.ts +80 -0
  7. package/corpus/core/src/cli/design-connect.ts +433 -151
  8. package/corpus/core/src/cli/skills.ts +14 -0
  9. package/corpus/core/src/client/AssistantChat.tsx +25 -0
  10. package/corpus/core/src/client/MultiTabAssistantChat.tsx +40 -6
  11. package/corpus/core/src/client/agent-chat.ts +165 -1
  12. package/corpus/core/src/client/chat/index.ts +9 -1
  13. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +38 -2
  14. package/corpus/core/src/client/index.ts +5 -0
  15. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +18 -7
  16. package/corpus/core/src/collab/awareness-store.ts +23 -12
  17. package/corpus/core/src/collab/client.ts +16 -5
  18. package/corpus/core/src/collab/index.ts +4 -0
  19. package/corpus/core/src/collab/recent-edits.ts +65 -2
  20. package/corpus/core/src/deploy/build.ts +133 -5
  21. package/corpus/core/src/index.browser.ts +1 -0
  22. package/corpus/core/src/server/agent-chat-plugin.ts +98 -11
  23. package/corpus/core/src/server/builder-design-systems.ts +30 -4
  24. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +1 -1
  25. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +12 -6
  26. package/corpus/templates/clips/AGENTS.md +4 -0
  27. package/corpus/templates/clips/actions/create-recording.ts +2 -0
  28. package/corpus/templates/clips/actions/lib/create-recording-schema.ts +5 -3
  29. package/corpus/templates/clips/actions/request-transcript.ts +102 -33
  30. package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -0
  31. package/corpus/templates/clips/app/components/player/share-dialog.tsx +59 -19
  32. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +48 -4
  33. package/corpus/templates/clips/app/components/player/video-player.tsx +17 -12
  34. package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
  35. package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
  36. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  37. package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
  38. package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
  39. package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
  40. package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
  41. package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
  42. package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
  43. package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
  44. package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
  45. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -37
  46. package/corpus/templates/clips/app/routes/record.tsx +17 -4
  47. package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
  48. package/corpus/templates/clips/changelog/2026-07-09-desktop-popover-sits-closer-to-the-menu-bar.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-09-desktop-recording-optimization-progress-stays-visible-until-the-clip-opens.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-07-09-fixed-large-video-file-uploads-failing-before-their-resumabl.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-07-09-fixed-new-video-uploads-defaulting-to-private.md +6 -0
  52. package/corpus/templates/clips/changelog/2026-07-09-long-recordings-now-get-enough-time-to-download-their-media-.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-07-09-meeting-notes-no-longer-lowers-your-microphone-volume-for-ot.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-09-on-mobile-tapping-a-playing-clip-reveals-playback-controls-w.md +6 -0
  55. package/corpus/templates/clips/changelog/2026-07-09-regenerate-any-clip-transcript-from-its-saved-recording-or-r.md +6 -0
  56. package/corpus/templates/clips/changelog/2026-07-09-slack-previews-now-show-the-clip-length-alongside-its-descri.md +6 -0
  57. package/corpus/templates/clips/desktop/src/app.tsx +12 -2
  58. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +8 -1
  59. package/corpus/templates/clips/desktop/src/lib/recorder.ts +8 -5
  60. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +5 -5
  61. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +3 -0
  62. package/corpus/templates/clips/desktop/src/styles.css +6 -3
  63. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +29 -11
  64. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
  65. package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +103 -21
  66. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +117 -39
  67. package/corpus/templates/clips/server/lib/slack-unfurls.ts +20 -1
  68. package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +10 -4
  69. package/corpus/templates/clips/shared/recording-core.ts +12 -0
  70. package/corpus/templates/content/AGENTS.md +7 -6
  71. package/corpus/templates/content/actions/_document-flush.ts +132 -17
  72. package/corpus/templates/content/actions/_notion-action-utils.ts +15 -0
  73. package/corpus/templates/content/actions/create-and-link-notion-page.ts +2 -0
  74. package/corpus/templates/content/actions/link-notion-page.ts +2 -0
  75. package/corpus/templates/content/actions/pull-document.ts +5 -7
  76. package/corpus/templates/content/actions/pull-notion-page.ts +2 -0
  77. package/corpus/templates/content/actions/push-notion-page.ts +10 -0
  78. package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +2 -0
  79. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +88 -23
  80. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +32 -2
  81. package/corpus/templates/content/app/hooks/use-notion.ts +6 -6
  82. package/corpus/templates/content/app/i18n/zh-TW.ts +1 -0
  83. package/corpus/templates/content/app/i18n-data.ts +18 -0
  84. package/corpus/templates/content/changelog/2026-07-09-notion-conflict-choices-and-version-restores-preserve-docume.md +6 -0
  85. package/corpus/templates/content/server/lib/notion-sync.ts +80 -64
  86. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +29 -2
  87. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +86 -0
  88. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +57 -0
  89. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +11 -0
  90. package/corpus/templates/design/AGENTS.md +45 -10
  91. package/corpus/templates/design/actions/add-localhost-screens.ts +66 -13
  92. package/corpus/templates/design/actions/apply-component-prop-edit.ts +45 -36
  93. package/corpus/templates/design/actions/apply-motion-edit.ts +25 -31
  94. package/corpus/templates/design/actions/apply-shader-fill.ts +29 -42
  95. package/corpus/templates/design/actions/detach-component-instance.ts +337 -0
  96. package/corpus/templates/design/actions/export-design-as-figma-svg.ts +219 -0
  97. package/corpus/templates/design/actions/generate-design.ts +79 -12
  98. package/corpus/templates/design/actions/generate-screens.ts +57 -12
  99. package/corpus/templates/design/actions/get-figma-styles.ts +115 -0
  100. package/corpus/templates/design/actions/go-to-main-component.ts +254 -0
  101. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +4 -3
  102. package/corpus/templates/design/actions/import-design-source.ts +4 -31
  103. package/corpus/templates/design/actions/import-figma-clipboard.ts +134 -0
  104. package/corpus/templates/design/actions/import-figma-frame.ts +95 -0
  105. package/corpus/templates/design/actions/index-design-system-with-builder.ts +16 -1
  106. package/corpus/templates/design/actions/list-design-components.ts +104 -0
  107. package/corpus/templates/design/actions/list-figma-library-assets.ts +1 -24
  108. package/corpus/templates/design/actions/open-component-source.ts +2 -4
  109. package/corpus/templates/design/actions/present-design-variants.ts +412 -16
  110. package/corpus/templates/design/actions/preview-source-edit.ts +14 -1
  111. package/corpus/templates/design/actions/run-design-audit.ts +25 -3
  112. package/corpus/templates/design/actions/show-design-questions.ts +10 -0
  113. package/corpus/templates/design/actions/swap-component-instance.ts +544 -0
  114. package/corpus/templates/design/actions/take-design-screenshot.ts +45 -60
  115. package/corpus/templates/design/actions/write-local-file.ts +25 -1
  116. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +152 -0
  117. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1077 -58
  118. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +5 -5
  119. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +73 -21
  120. package/corpus/templates/design/app/components/design/EditPanel.tsx +16 -11
  121. package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +364 -0
  122. package/corpus/templates/design/app/components/design/LayersPanel.tsx +180 -53
  123. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +0 -2
  124. package/corpus/templates/design/app/components/design/MotionDock.tsx +144 -21
  125. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1152 -167
  126. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +27 -5
  127. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +23 -2
  128. package/corpus/templates/design/app/components/design/TokensPanel.tsx +63 -49
  129. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1293 -54
  130. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +7 -0
  131. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +6 -1
  132. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +23 -6
  133. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +7 -0
  134. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +5 -0
  135. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +14 -2
  136. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +37 -2
  137. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open-guard.ts +25 -0
  138. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +19 -1
  139. package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +43 -5
  140. package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +27 -2
  141. package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +10 -1
  142. package/corpus/templates/design/app/components/design/code-workbench/store.tsx +19 -2
  143. package/corpus/templates/design/app/components/design/design-canvas/annotation-snapshot.ts +265 -0
  144. package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +16 -5
  145. package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +16 -2
  146. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +7 -2
  147. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +3 -1
  148. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +78 -8
  149. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +451 -73
  150. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +12 -4
  151. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +398 -272
  152. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +41 -7
  153. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +16 -3
  154. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +24 -4
  155. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +222 -4
  156. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +171 -78
  157. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +45 -5
  158. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +299 -28
  159. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +120 -150
  160. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +53 -1
  161. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +284 -117
  162. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +81 -0
  163. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +19 -14
  164. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +7 -0
  165. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +155 -0
  166. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +268 -46
  167. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +350 -31
  168. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +13 -4
  169. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +233 -23
  170. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +72 -2
  171. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +51 -11
  172. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +84 -11
  173. package/corpus/templates/design/app/components/design/inspector/index.ts +3 -0
  174. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +552 -0
  175. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +157 -0
  176. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +53 -0
  177. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +184 -0
  178. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +403 -26
  179. package/corpus/templates/design/app/components/design/multi-screen/types.ts +20 -1
  180. package/corpus/templates/design/app/components/design/types.ts +33 -0
  181. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +59 -7
  182. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +119 -17
  183. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +275 -63
  184. package/corpus/templates/design/app/hooks/use-question-flow.ts +14 -3
  185. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +67 -10
  186. package/corpus/templates/design/app/i18n/zh-TW.ts +91 -1
  187. package/corpus/templates/design/app/i18n-data.ts +1217 -10
  188. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +577 -0
  189. package/corpus/templates/design/app/lib/agent-chat.ts +23 -0
  190. package/corpus/templates/design/app/lib/design-import.ts +24 -0
  191. package/corpus/templates/design/app/lib/figma-clipboard.ts +157 -0
  192. package/corpus/templates/design/app/lib/figma-svg-copy.ts +221 -0
  193. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +143 -30
  194. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +15 -0
  195. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +33 -0
  196. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +38 -0
  197. package/corpus/templates/design/app/pages/design-editor/overview-annotation-context.ts +157 -0
  198. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +62 -6
  199. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +245 -0
  200. package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +586 -0
  201. package/corpus/templates/design/app/pages/design-editor/screen-auto-layout.ts +169 -0
  202. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +201 -1
  203. package/corpus/templates/design/changelog/2026-07-09-added-underline-strikethrough-and-text-case-uppercase-lowerc.md +6 -0
  204. package/corpus/templates/design/changelog/2026-07-09-annotate-now-draws-across-the-all-screens-canvas-without-swi.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-09-auto-layout-children-can-now-be-dragged-into-freeform-screen.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-09-board-shapes-now-stay-visible-and-nest-correctly-across-the-.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-09-component-source-navigation-now-opens-reliably-from-the-insp.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-09-cross-screen-local-react-layer-moves-now-hand-off-exact-sour.md +6 -0
  209. package/corpus/templates/design/changelog/2026-07-09-cross-screen-moves-undo-and-redo-now-keep-both-screens-stabl.md +6 -0
  210. package/corpus/templates/design/changelog/2026-07-09-detach-a-component-instance-into-plain-editable-eleme.md +6 -0
  211. package/corpus/templates/design/changelog/2026-07-09-dragging-layers-into-auto-layout-now-respects-ignore-auto-la.md +6 -0
  212. package/corpus/templates/design/changelog/2026-07-09-drawing-annotations-sent-to-the-agent-now-include-a-rendered.md +6 -0
  213. package/corpus/templates/design/changelog/2026-07-09-editor-sidebars-now-use-figma-matched-default-widths-for-a-r.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-09-empty-frames-and-rectangles-now-expose-full-auto-layout-cont.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-09-export-any-design-screen-as-a-real-vector-svg-for-figma-with.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-09-grid-auto-layout-now-uses-true-responsive-rows-and-columns-w.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-09-hidden-and-locked-screens-now-behave-consistently-between-la.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-09-image-fills-now-preserve-valid-urls-and-layered-fit-settings.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-09-import-figma-frames-as-pixel-accurate-design-screens-via-a-s.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-09-inspect-code-now-hides-internal-styling-metadata-and-wraps-l.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-09-inspector-blur-and-shadow-effects-now-preserve-other-effects.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-09-jump-to-a-components-other-instances-from-the-inspecto.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-09-keyboard-shortcuts-are-now-discoverable-in-a-figma-style-bot.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-09-layer-selection-now-keeps-cmd-or-ctrl-toggles-and-shift-rang.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-09-layers-dropped-into-auto-layout-now-participate-correctly-in.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-09-local-live-edit-screens-now-enter-and-leave-full-view-withou.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-09-local-preview-urls-now-live-in-the-inspector-instead-of-taki.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-can-now-be-hidden-and-locked-from-layers-.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-now-keep-exact-source-provenance-and-supp.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-09-local-react-visual-editing-now-loads-reliably-and-shows-live.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-09-paste-from-figma-cmd-v-now-imports-exact-editable-nodes-when.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-09-pressing-escape-after-selecting-a-screen-now-reveals-page-pr.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-09-right-click-overlapping-or-nested-objects-to-choose-the-exac.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-09-scale-edge-stretch-and-center-constraints-now-preserve-layer.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-09-screen-rows-can-now-be-reordered-in-layers-with-matching-can.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-09-screen-selection-stays-responsive-in-designs-with-hundreds-o.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-09-shift-a-now-enables-auto-layout-on-a-selected-screen-with-fl.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-09-swap-a-component-instance-for-another-from-the-libra.md +6 -0
  239. package/corpus/templates/design/changelog/2026-07-10-drawing-annotations-now-stay-available-to-retry-when-the.md +6 -0
  240. package/corpus/templates/design/e2e/global-setup.ts +3 -6
  241. package/corpus/templates/design/e2e/helpers.ts +27 -6
  242. package/corpus/templates/design/server/lib/design-to-figma-svg.ts +1412 -0
  243. package/corpus/templates/design/server/lib/figma-clipboard-match.ts +196 -0
  244. package/corpus/templates/design/server/lib/figma-node-import.ts +311 -0
  245. package/corpus/templates/design/server/lib/figma-node-to-html.ts +1334 -0
  246. package/corpus/templates/design/server/lib/figma-paste-fallback.ts +64 -0
  247. package/corpus/templates/design/server/lib/playwright-runtime.ts +78 -0
  248. package/corpus/templates/design/server/plugins/db.ts +41 -0
  249. package/corpus/templates/design/server/source-workspace.ts +64 -0
  250. package/corpus/templates/design/shared/board-file.ts +13 -2
  251. package/corpus/templates/design/shared/canvas-math.ts +231 -33
  252. package/corpus/templates/design/shared/code-layer.ts +122 -8
  253. package/corpus/templates/design/shared/component-library.ts +161 -0
  254. package/corpus/templates/design/shared/db-conflict.ts +27 -0
  255. package/corpus/templates/design/shared/design-source-capabilities.ts +2 -1
  256. package/corpus/templates/design/shared/figma-url.ts +149 -0
  257. package/corpus/templates/design/shared/pen-path.ts +16 -7
  258. package/corpus/templates/design/shared/source-mode.ts +1 -0
  259. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +7 -19
  260. package/corpus/templates/plan/changelog/2026-07-09-canvas-panning-now-keeps-zoom-fixed-even-at-the-edges.md +6 -0
  261. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +16 -1
  262. package/corpus/templates/slides/app/context/DeckContext.tsx +311 -70
  263. package/corpus/templates/slides/changelog/2026-07-09-fixed-the-slide-rail-going-permanently-stale-during-a-long-a.md +6 -0
  264. package/dist/agent/production-agent.d.ts +79 -6
  265. package/dist/agent/production-agent.d.ts.map +1 -1
  266. package/dist/agent/production-agent.js +161 -14
  267. package/dist/agent/production-agent.js.map +1 -1
  268. package/dist/agent/run-manager.d.ts.map +1 -1
  269. package/dist/agent/run-manager.js +29 -5
  270. package/dist/agent/run-manager.js.map +1 -1
  271. package/dist/agent/run-store.d.ts +46 -0
  272. package/dist/agent/run-store.d.ts.map +1 -1
  273. package/dist/agent/run-store.js +61 -0
  274. package/dist/agent/run-store.js.map +1 -1
  275. package/dist/cli/design-connect.d.ts +6 -0
  276. package/dist/cli/design-connect.d.ts.map +1 -1
  277. package/dist/cli/design-connect.js +336 -139
  278. package/dist/cli/design-connect.js.map +1 -1
  279. package/dist/cli/skills.d.ts.map +1 -1
  280. package/dist/cli/skills.js +14 -0
  281. package/dist/cli/skills.js.map +1 -1
  282. package/dist/client/AssistantChat.d.ts +2 -0
  283. package/dist/client/AssistantChat.d.ts.map +1 -1
  284. package/dist/client/AssistantChat.js +19 -3
  285. package/dist/client/AssistantChat.js.map +1 -1
  286. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  287. package/dist/client/MultiTabAssistantChat.js +38 -18
  288. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  289. package/dist/client/agent-chat.d.ts +60 -0
  290. package/dist/client/agent-chat.d.ts.map +1 -1
  291. package/dist/client/agent-chat.js +122 -1
  292. package/dist/client/agent-chat.js.map +1 -1
  293. package/dist/client/chat/index.d.ts +1 -1
  294. package/dist/client/chat/index.d.ts.map +1 -1
  295. package/dist/client/chat/index.js +1 -1
  296. package/dist/client/chat/index.js.map +1 -1
  297. package/dist/client/extensions/EmbeddedExtension.d.ts +12 -0
  298. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  299. package/dist/client/extensions/EmbeddedExtension.js +36 -2
  300. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  301. package/dist/client/index.d.ts +1 -1
  302. package/dist/client/index.d.ts.map +1 -1
  303. package/dist/client/index.js +1 -1
  304. package/dist/client/index.js.map +1 -1
  305. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  306. package/dist/client/rich-markdown-editor/useCollabReconcile.js +18 -6
  307. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  308. package/dist/collab/awareness-store.d.ts +5 -0
  309. package/dist/collab/awareness-store.d.ts.map +1 -1
  310. package/dist/collab/awareness-store.js +19 -12
  311. package/dist/collab/awareness-store.js.map +1 -1
  312. package/dist/collab/awareness.d.ts +2 -2
  313. package/dist/collab/awareness.d.ts.map +1 -1
  314. package/dist/collab/client.d.ts.map +1 -1
  315. package/dist/collab/client.js +13 -5
  316. package/dist/collab/client.js.map +1 -1
  317. package/dist/collab/index.d.ts +1 -0
  318. package/dist/collab/index.d.ts.map +1 -1
  319. package/dist/collab/index.js +1 -0
  320. package/dist/collab/index.js.map +1 -1
  321. package/dist/collab/recent-edits.d.ts +3 -1
  322. package/dist/collab/recent-edits.d.ts.map +1 -1
  323. package/dist/collab/recent-edits.js +57 -2
  324. package/dist/collab/recent-edits.js.map +1 -1
  325. package/dist/collab/routes.d.ts +1 -1
  326. package/dist/deploy/build.d.ts +24 -0
  327. package/dist/deploy/build.d.ts.map +1 -1
  328. package/dist/deploy/build.js +102 -5
  329. package/dist/deploy/build.js.map +1 -1
  330. package/dist/index.browser.d.ts +1 -1
  331. package/dist/index.browser.d.ts.map +1 -1
  332. package/dist/index.browser.js +1 -1
  333. package/dist/index.browser.js.map +1 -1
  334. package/dist/notifications/routes.d.ts +1 -1
  335. package/dist/observability/routes.d.ts +5 -5
  336. package/dist/progress/routes.d.ts +1 -1
  337. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  338. package/dist/server/agent-chat-plugin.js +87 -10
  339. package/dist/server/agent-chat-plugin.js.map +1 -1
  340. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  341. package/dist/server/builder-design-systems.d.ts +15 -0
  342. package/dist/server/builder-design-systems.d.ts.map +1 -1
  343. package/dist/server/builder-design-systems.js +9 -4
  344. package/dist/server/builder-design-systems.js.map +1 -1
  345. package/package.json +1 -1
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Conservative matcher: figures out which top-level Figma frame(s) a clipboard
3
+ * paste corresponds to, without ever risking an unintended whole-file import.
4
+ *
5
+ * Why this exists: Figma's clipboard `figmeta` marker (see
6
+ * `app/lib/figma-clipboard.ts`) only carries a `fileKey` — it has no node ids,
7
+ * and the `pasteID` it does carry is server-side/ephemeral (not resolvable to
8
+ * a node through the public REST API). So a clipboard paste alone can't name
9
+ * an exact node the way a copied frame LINK can (`?node-id=...`). Instead we
10
+ * fetch the file's shallow structure (see `fetchFileStructure(fileKey, 3)` in
11
+ * `figma-node-import.ts`: pages -> top-level frames -> their direct children)
12
+ * and heuristically match it against the *visible* clipboard HTML fallback
13
+ * (frame/layer names and text layer contents tend to reappear as literal text
14
+ * in that fallback markup).
15
+ *
16
+ * This is deliberately conservative: pure name/text equality only, no fuzzy
17
+ * matching, and it only ever returns a `matched` result when the evidence is
18
+ * unambiguous. Anything else — `none` or `ambiguous` — must fall back to the
19
+ * legacy HTML-paste path (see `import-figma-clipboard.ts`) rather than guess
20
+ * at a node import, since importing the wrong node is worse than importing a
21
+ * lossy but honest HTML approximation.
22
+ */
23
+
24
+ import type { FigmaFileDepthNode } from "./figma-node-import.js";
25
+
26
+ export interface FigmaNodeCandidate {
27
+ id: string;
28
+ name: string;
29
+ /** Text layer `characters` found among this node's direct children. */
30
+ texts: string[];
31
+ }
32
+
33
+ export type FigmaClipboardMatchStatus = "matched" | "ambiguous" | "none";
34
+
35
+ export interface FigmaClipboardMatch {
36
+ id: string;
37
+ name: string;
38
+ reason: "name" | "text";
39
+ }
40
+
41
+ export interface FigmaClipboardMatchResult {
42
+ status: FigmaClipboardMatchStatus;
43
+ matches: FigmaClipboardMatch[];
44
+ }
45
+
46
+ /** Caps a "multi-select copy" match so a huge/garbled name-match list still degrades to ambiguous. */
47
+ const MAX_MULTI_MATCH = 8;
48
+ /** Minimum distinct visible-text overlaps required before trusting a text-only match (no name match). */
49
+ const MIN_TEXT_MATCHES = 2;
50
+
51
+ function normalize(value: string): string {
52
+ return value.trim().toLowerCase().replace(/\s+/g, " ");
53
+ }
54
+
55
+ /**
56
+ * Extracts distinct non-empty visible text lines from clipboard fallback HTML
57
+ * (already stripped of the hidden figmeta/figbuffer markers by
58
+ * `parseVisibleClipboardHtml`). Deliberately simple: strip tags, split on the
59
+ * resulting line breaks, trim, dedupe — good enough for literal-text overlap
60
+ * matching without pulling in an HTML parser.
61
+ */
62
+ export function extractVisibleTexts(html: string | undefined | null): string[] {
63
+ if (!html) return [];
64
+ const withoutTagContent = html
65
+ .replace(/<script[\s\S]*?<\/script>/gi, " ")
66
+ .replace(/<style[\s\S]*?<\/style>/gi, " ")
67
+ .replace(/<[^>]+>/g, "\n");
68
+ const decoded = withoutTagContent
69
+ .replace(/&nbsp;/gi, " ")
70
+ .replace(/&amp;/gi, "&")
71
+ .replace(/&lt;/gi, "<")
72
+ .replace(/&gt;/gi, ">")
73
+ .replace(/&quot;/gi, '"')
74
+ .replace(/&#0?39;/gi, "'");
75
+ const seen = new Set<string>();
76
+ const out: string[] = [];
77
+ for (const rawLine of decoded.split("\n")) {
78
+ const line = rawLine.trim();
79
+ if (!line || seen.has(line)) continue;
80
+ seen.add(line);
81
+ out.push(line);
82
+ }
83
+ return out;
84
+ }
85
+
86
+ function collectTextCharacters(node: FigmaFileDepthNode | undefined): string[] {
87
+ if (!node) return [];
88
+ const out: string[] = [];
89
+ const characters = (node as { characters?: unknown }).characters;
90
+ if (typeof characters === "string" && characters.trim()) out.push(characters);
91
+ for (const child of node.children ?? [])
92
+ out.push(...collectTextCharacters(child));
93
+ return out;
94
+ }
95
+
96
+ /**
97
+ * Builds match candidates from a `depth=3` file-structure fetch: one
98
+ * candidate per top-level frame across every page, each carrying its own
99
+ * name plus the visible text found among its (one level of) children.
100
+ * Intentionally scoped to top-level frames only — never deeper — so a match
101
+ * can only ever resolve to whole frames a user could plausibly have copied,
102
+ * not to arbitrary nested layers.
103
+ */
104
+ export function buildFigmaNodeCandidates(
105
+ document: FigmaFileDepthNode | undefined,
106
+ ): FigmaNodeCandidate[] {
107
+ const candidates: FigmaNodeCandidate[] = [];
108
+ for (const page of document?.children ?? []) {
109
+ for (const frame of page.children ?? []) {
110
+ if (!frame?.id) continue;
111
+ candidates.push({
112
+ id: frame.id,
113
+ name: frame.name ?? "",
114
+ texts: collectTextCharacters(frame),
115
+ });
116
+ }
117
+ }
118
+ return candidates;
119
+ }
120
+
121
+ /**
122
+ * Decision rules (see module doc for the "why"):
123
+ *
124
+ * 1. Any candidate whose *name* exactly equals (case/whitespace-insensitive)
125
+ * one of the clipboard's visible text lines is a "name match" — the
126
+ * strongest signal, since a frame's name reappearing verbatim in the copy
127
+ * is unlikely by chance.
128
+ * - Exactly one name match -> matched (single frame copy).
129
+ * - 2..MAX_MULTI_MATCH name matches -> matched, all of them (multi-select
130
+ * copy of several named frames).
131
+ * - More than MAX_MULTI_MATCH -> ambiguous (too many to trust).
132
+ * 2. No name match: fall back to counting distinct visible-text overlaps
133
+ * between each candidate's own text layers and the clipboard text.
134
+ * - Exactly one candidate reaches MIN_TEXT_MATCHES -> matched (text
135
+ * match).
136
+ * - Zero or 2+ candidates reach MIN_TEXT_MATCHES -> ambiguous/none.
137
+ * 3. No candidate clears either bar -> none.
138
+ */
139
+ export function matchFigmaClipboardNodes(
140
+ candidates: FigmaNodeCandidate[],
141
+ clipboardTexts: string[],
142
+ ): FigmaClipboardMatchResult {
143
+ const clipboardTextSet = new Set(
144
+ clipboardTexts.map(normalize).filter((text) => text.length > 0),
145
+ );
146
+
147
+ const nameMatches = candidates.filter((candidate) =>
148
+ clipboardTextSet.has(normalize(candidate.name)),
149
+ );
150
+
151
+ if (nameMatches.length === 1) {
152
+ const [match] = nameMatches;
153
+ return {
154
+ status: "matched",
155
+ matches: [{ id: match!.id, name: match!.name, reason: "name" }],
156
+ };
157
+ }
158
+ if (nameMatches.length > 1) {
159
+ if (nameMatches.length > MAX_MULTI_MATCH) {
160
+ return { status: "ambiguous", matches: [] };
161
+ }
162
+ return {
163
+ status: "matched",
164
+ matches: nameMatches.map((match) => ({
165
+ id: match.id,
166
+ name: match.name,
167
+ reason: "name" as const,
168
+ })),
169
+ };
170
+ }
171
+
172
+ const textScores = candidates.map((candidate) => {
173
+ const matchedTexts = new Set(
174
+ candidate.texts
175
+ .map(normalize)
176
+ .filter((text) => text.length > 0 && clipboardTextSet.has(text)),
177
+ );
178
+ return { candidate, score: matchedTexts.size };
179
+ });
180
+ const strongTextMatches = textScores.filter(
181
+ (entry) => entry.score >= MIN_TEXT_MATCHES,
182
+ );
183
+
184
+ if (strongTextMatches.length === 1) {
185
+ const { candidate } = strongTextMatches[0]!;
186
+ return {
187
+ status: "matched",
188
+ matches: [{ id: candidate.id, name: candidate.name, reason: "text" }],
189
+ };
190
+ }
191
+ if (strongTextMatches.length > 1) {
192
+ return { status: "ambiguous", matches: [] };
193
+ }
194
+
195
+ return { status: "none", matches: [] };
196
+ }
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Shared Figma REST fetch -> map -> screen-file core.
3
+ *
4
+ * Extracted from `import-figma-frame.ts` so a second import path (paste-driven
5
+ * node resolution, see `figma-clipboard-match.ts` / `import-figma-clipboard.ts`)
6
+ * can reuse the exact same fetch/map logic instead of re-implementing it.
7
+ * `import-figma-frame.ts` still owns the action interface and result shape; this
8
+ * module only owns the parts that talk to the Figma REST API and turn node JSON
9
+ * into `ImportedDesignFile` records.
10
+ *
11
+ * Pure/network-boundary split mirrors `figma-node-to-html.ts`'s own doc comment:
12
+ * this module fetches, `figma-node-to-html.ts` maps (pure, synchronous).
13
+ */
14
+
15
+ import {
16
+ collectFallbackNodeIds,
17
+ collectImageFillRefs,
18
+ mapFigmaNodeToHtml,
19
+ type FidelityEntry,
20
+ type FidelityLevel,
21
+ type FigmaNode,
22
+ } from "./figma-node-to-html.js";
23
+ import {
24
+ normalizeImportedHtmlDocument,
25
+ type ImportedDesignFile,
26
+ } from "./import-design-files.js";
27
+ import { executeProviderApiRequest } from "./provider-api.js";
28
+
29
+ /**
30
+ * Figma's box model treats a frame's declared width/height as the OUTER
31
+ * (border-box-equivalent) size: padding eats into the interior without
32
+ * growing the frame's footprint. The browser default is `box-sizing:
33
+ * content-box`, so `figma-node-to-html.ts`'s per-node inline `width`/`height`
34
+ * (mapped 1:1 from `absoluteBoundingBox`) plus any padding on the same node
35
+ * renders LARGER than Figma intends by exactly the padding amount, and the
36
+ * default UA `body { margin: 8px }` additionally offsets the whole imported
37
+ * screen away from (0,0). Both together produce visible horizontal/vertical
38
+ * overflow and a diagonal pixel offset relative to Figma's own render for any
39
+ * auto-layout frame with padding (i.e. most real designs). Scope the reset to
40
+ * this Figma-import pipeline only — the shared `normalizeImportedHtmlDocument`
41
+ * is also used by non-Figma import paths that must not be affected.
42
+ */
43
+ function withFigmaBoxModelReset(html: string): string {
44
+ return `<style>*,*::before,*::after{box-sizing:border-box;}body{margin:0;}</style>\n${html}`;
45
+ }
46
+
47
+ type FigmaProviderEnvelope = {
48
+ response?: {
49
+ ok?: boolean;
50
+ status?: number;
51
+ statusText?: string;
52
+ json?: unknown;
53
+ text?: string;
54
+ };
55
+ };
56
+
57
+ export function providerJson(envelope: unknown, label: string): unknown {
58
+ const response = (envelope as FigmaProviderEnvelope | null)?.response;
59
+ if (!response) throw new Error(`Figma ${label} response was empty.`);
60
+ if (response.ok === false) {
61
+ const detail =
62
+ (typeof response.text === "string" && response.text.trim()) ||
63
+ response.statusText ||
64
+ `HTTP ${response.status ?? "error"}`;
65
+ throw new Error(`Figma ${label} request failed: ${detail}`);
66
+ }
67
+ return response.json;
68
+ }
69
+
70
+ export async function figmaGet(path: string, query?: Record<string, unknown>) {
71
+ return executeProviderApiRequest({
72
+ provider: "figma",
73
+ method: "GET",
74
+ path,
75
+ query,
76
+ maxBytes: 8 * 1024 * 1024,
77
+ });
78
+ }
79
+
80
+ export interface FigmaFileDepthNode {
81
+ id?: string;
82
+ name?: string;
83
+ type?: string;
84
+ children?: FigmaFileDepthNode[];
85
+ characters?: string;
86
+ }
87
+
88
+ /**
89
+ * Fetches the file's page/frame structure at a given `depth` (cheap: no
90
+ * geometry, just ids/names/types/children/characters). `depth=2` returns
91
+ * pages + their direct children (top-level frames) — used to find a default
92
+ * frame when no node-id is given. `depth=3` additionally returns each top
93
+ * frame's direct children — used by the clipboard matcher to read visible
94
+ * text for heuristic matching.
95
+ */
96
+ export async function fetchFileStructure(
97
+ fileKey: string,
98
+ depth: number,
99
+ ): Promise<FigmaFileDepthNode> {
100
+ const envelope = await figmaGet(`/files/${fileKey}`, { depth });
101
+ const json = providerJson(envelope, "file") as {
102
+ document?: FigmaFileDepthNode;
103
+ };
104
+ return json.document ?? {};
105
+ }
106
+
107
+ export async function resolveTargetNodeId(
108
+ fileKey: string,
109
+ nodeId: string | null,
110
+ ): Promise<string> {
111
+ if (nodeId) return nodeId;
112
+
113
+ // No node-id given: find the file's first top-level frame under its first
114
+ // page. depth=2 keeps this cheap (pages + their direct children only, no
115
+ // deep geometry) instead of pulling the entire document tree.
116
+ const document = await fetchFileStructure(fileKey, 2);
117
+ const firstPage = document.children?.[0];
118
+ const firstFrame = firstPage?.children?.find((child) => Boolean(child?.id));
119
+ if (!firstFrame?.id) {
120
+ throw new Error(
121
+ "Could not find a frame to import. Pass a specific node-id or a Figma frame URL with ?node-id=.",
122
+ );
123
+ }
124
+ return firstFrame.id;
125
+ }
126
+
127
+ /**
128
+ * Fetches one or more nodes' full document JSON (with vector path geometry)
129
+ * in a single request — the Figma nodes endpoint accepts a comma-joined
130
+ * `ids` list. Throws immediately on the first node id that is missing,
131
+ * errored, or has no document payload, naming that specific node id.
132
+ */
133
+ export async function fetchFigmaNodes(
134
+ fileKey: string,
135
+ nodeIds: string[],
136
+ ): Promise<Record<string, FigmaNode>> {
137
+ if (nodeIds.length === 0) return {};
138
+ const envelope = await figmaGet(`/files/${fileKey}/nodes`, {
139
+ ids: nodeIds.join(","),
140
+ geometry: "paths",
141
+ });
142
+ const json = providerJson(envelope, "nodes") as {
143
+ nodes?: Record<string, { document?: FigmaNode; err?: string } | undefined>;
144
+ };
145
+ const result: Record<string, FigmaNode> = {};
146
+ for (const nodeId of nodeIds) {
147
+ const entry = json.nodes?.[nodeId];
148
+ if (!entry) {
149
+ throw new Error(
150
+ `Figma node ${nodeId} was not found in file ${fileKey}. Check the node-id and that the token has access to this file.`,
151
+ );
152
+ }
153
+ if (entry.err) {
154
+ throw new Error(
155
+ `Figma returned an error for node ${nodeId}: ${entry.err}`,
156
+ );
157
+ }
158
+ if (!entry.document) {
159
+ throw new Error(`Figma node ${nodeId} had no document payload.`);
160
+ }
161
+ result[nodeId] = entry.document;
162
+ }
163
+ return result;
164
+ }
165
+
166
+ export async function fetchFigmaNode(
167
+ fileKey: string,
168
+ nodeId: string,
169
+ ): Promise<FigmaNode> {
170
+ const nodes = await fetchFigmaNodes(fileKey, [nodeId]);
171
+ return nodes[nodeId]!;
172
+ }
173
+
174
+ async function fetchFallbackImageUrls(
175
+ fileKey: string,
176
+ nodeIds: string[],
177
+ ): Promise<Record<string, string>> {
178
+ if (nodeIds.length === 0) return {};
179
+ const envelope = await figmaGet(`/images/${fileKey}`, {
180
+ ids: nodeIds.join(","),
181
+ format: "png",
182
+ scale: 2,
183
+ });
184
+ const json = providerJson(envelope, "images") as {
185
+ images?: Record<string, string | null | undefined>;
186
+ };
187
+ const result: Record<string, string> = {};
188
+ for (const [id, url] of Object.entries(json.images ?? {})) {
189
+ if (typeof url === "string" && url) result[id] = url;
190
+ }
191
+ return result;
192
+ }
193
+
194
+ async function fetchImageFillUrls(
195
+ fileKey: string,
196
+ imageRefs: string[],
197
+ ): Promise<Record<string, string>> {
198
+ if (imageRefs.length === 0) return {};
199
+ const envelope = await figmaGet(`/files/${fileKey}/images`);
200
+ const json = providerJson(envelope, "image fills") as {
201
+ images?: Record<string, string | null | undefined>;
202
+ };
203
+ const result: Record<string, string> = {};
204
+ for (const ref of imageRefs) {
205
+ const url = json.images?.[ref];
206
+ if (typeof url === "string" && url) result[ref] = url;
207
+ }
208
+ return result;
209
+ }
210
+
211
+ export function sanitizeTitle(
212
+ name: string | undefined,
213
+ fallback: string,
214
+ ): string {
215
+ const trimmed = name?.trim();
216
+ if (!trimmed) return fallback;
217
+ return (
218
+ trimmed
219
+ .replace(/[^\w. -]+/g, "-")
220
+ .replace(/\s+/g, "-")
221
+ .slice(0, 80) || fallback
222
+ );
223
+ }
224
+
225
+ export function summarizeFidelity(entries: FidelityEntry[]) {
226
+ const byLevel = (level: FidelityLevel) =>
227
+ entries
228
+ .filter((entry) => entry.level === level)
229
+ .map((entry) => ({
230
+ nodeId: entry.nodeId,
231
+ nodeName: entry.nodeName,
232
+ nodeType: entry.nodeType,
233
+ notes: entry.notes,
234
+ }));
235
+ return {
236
+ exactCount: entries.filter((entry) => entry.level === "exact").length,
237
+ approximated: byLevel("approximated"),
238
+ imageFallbacks: byLevel("image-fallback"),
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Fetches whatever PNG fallback / image-fill URLs the given nodes need (union
244
+ * across all of them, one request each instead of one per node) and maps
245
+ * every node to an `ImportedDesignFile`. Cascading x-offset placement mirrors
246
+ * `saveImportedDesignFiles`' own frame layout so multiple imported nodes don't
247
+ * land stacked on top of each other before the canvas placement pass runs.
248
+ */
249
+ export async function buildScreenFilesFromFigmaNodes(
250
+ fileKey: string,
251
+ nodesById: Record<string, FigmaNode>,
252
+ options: {
253
+ source?: (nodeId: string, node: FigmaNode) => Record<string, unknown>;
254
+ sourceLabel?: (nodeId: string, node: FigmaNode) => string;
255
+ } = {},
256
+ ): Promise<{ files: ImportedDesignFile[]; fidelityEntries: FidelityEntry[] }> {
257
+ const entries = Object.entries(nodesById);
258
+ const fallbackNodeIds = new Set<string>();
259
+ const imageFillRefs = new Set<string>();
260
+ for (const [, node] of entries) {
261
+ for (const id of collectFallbackNodeIds(node)) fallbackNodeIds.add(id);
262
+ for (const ref of collectImageFillRefs(node)) imageFillRefs.add(ref);
263
+ }
264
+
265
+ const [fallbackImageUrls, imageFillUrls] = await Promise.all([
266
+ fetchFallbackImageUrls(fileKey, Array.from(fallbackNodeIds)),
267
+ fetchImageFillUrls(fileKey, Array.from(imageFillRefs)),
268
+ ]);
269
+
270
+ const files: ImportedDesignFile[] = [];
271
+ const fidelityEntries: FidelityEntry[] = [];
272
+
273
+ for (const [nodeId, node] of entries) {
274
+ const { html, fidelity } = mapFigmaNodeToHtml(node, {
275
+ fallbackImageUrls,
276
+ imageFillUrls,
277
+ });
278
+ fidelityEntries.push(...fidelity.entries);
279
+
280
+ const title = sanitizeTitle(
281
+ node.name,
282
+ `figma-${nodeId.replace(/[:;]/g, "-")}`,
283
+ );
284
+ const sourceLabel =
285
+ options.sourceLabel?.(nodeId, node) ??
286
+ `Figma file ${fileKey}, node ${nodeId}`;
287
+ const content = normalizeImportedHtmlDocument(
288
+ withFigmaBoxModelReset(html || "<div></div>"),
289
+ sourceLabel,
290
+ );
291
+ files.push({
292
+ filename: `${title}.html`,
293
+ fileType: "html",
294
+ content,
295
+ source: {
296
+ sourceType: "figma-import",
297
+ figmaFileKey: fileKey,
298
+ figmaNodeId: nodeId,
299
+ figmaNodeName: node.name ?? null,
300
+ ...options.source?.(nodeId, node),
301
+ },
302
+ preferredFrame: {
303
+ title: node.name,
304
+ width: node.absoluteBoundingBox?.width,
305
+ height: node.absoluteBoundingBox?.height,
306
+ },
307
+ });
308
+ }
309
+
310
+ return { files, fidelityEntries };
311
+ }