@agent-native/core 0.92.10 → 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 (334) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +23 -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/index.browser.ts +1 -0
  21. package/corpus/core/src/server/agent-chat-plugin.ts +98 -11
  22. package/corpus/core/src/server/builder-design-systems.ts +30 -4
  23. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +1 -1
  24. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +12 -6
  25. package/corpus/templates/clips/AGENTS.md +4 -0
  26. package/corpus/templates/clips/actions/create-recording.ts +2 -0
  27. package/corpus/templates/clips/actions/lib/create-recording-schema.ts +5 -3
  28. package/corpus/templates/clips/actions/request-transcript.ts +102 -33
  29. package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -0
  30. package/corpus/templates/clips/app/components/player/share-dialog.tsx +59 -19
  31. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +48 -4
  32. package/corpus/templates/clips/app/components/player/video-player.tsx +17 -12
  33. package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
  34. package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
  35. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  36. package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
  37. package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
  38. package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
  39. package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
  40. package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
  41. package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
  42. package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
  43. package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
  44. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -37
  45. package/corpus/templates/clips/app/routes/record.tsx +17 -4
  46. package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
  47. package/corpus/templates/clips/changelog/2026-07-09-desktop-popover-sits-closer-to-the-menu-bar.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-07-09-desktop-recording-optimization-progress-stays-visible-until-the-clip-opens.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-09-fixed-large-video-file-uploads-failing-before-their-resumabl.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-07-09-fixed-new-video-uploads-defaulting-to-private.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-07-09-long-recordings-now-get-enough-time-to-download-their-media-.md +6 -0
  52. package/corpus/templates/clips/changelog/2026-07-09-meeting-notes-no-longer-lowers-your-microphone-volume-for-ot.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-07-09-on-mobile-tapping-a-playing-clip-reveals-playback-controls-w.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-09-regenerate-any-clip-transcript-from-its-saved-recording-or-r.md +6 -0
  55. package/corpus/templates/clips/changelog/2026-07-09-slack-previews-now-show-the-clip-length-alongside-its-descri.md +6 -0
  56. package/corpus/templates/clips/desktop/src/app.tsx +12 -2
  57. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +8 -1
  58. package/corpus/templates/clips/desktop/src/lib/recorder.ts +8 -5
  59. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +5 -5
  60. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +3 -0
  61. package/corpus/templates/clips/desktop/src/styles.css +6 -3
  62. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +29 -11
  63. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
  64. package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +103 -21
  65. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +117 -39
  66. package/corpus/templates/clips/server/lib/slack-unfurls.ts +20 -1
  67. package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +10 -4
  68. package/corpus/templates/clips/shared/recording-core.ts +12 -0
  69. package/corpus/templates/content/AGENTS.md +7 -6
  70. package/corpus/templates/content/actions/_document-flush.ts +132 -17
  71. package/corpus/templates/content/actions/_notion-action-utils.ts +15 -0
  72. package/corpus/templates/content/actions/create-and-link-notion-page.ts +2 -0
  73. package/corpus/templates/content/actions/link-notion-page.ts +2 -0
  74. package/corpus/templates/content/actions/pull-document.ts +5 -7
  75. package/corpus/templates/content/actions/pull-notion-page.ts +2 -0
  76. package/corpus/templates/content/actions/push-notion-page.ts +10 -0
  77. package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +2 -0
  78. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +88 -23
  79. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +32 -2
  80. package/corpus/templates/content/app/hooks/use-notion.ts +6 -6
  81. package/corpus/templates/content/app/i18n/zh-TW.ts +1 -0
  82. package/corpus/templates/content/app/i18n-data.ts +18 -0
  83. package/corpus/templates/content/changelog/2026-07-09-notion-conflict-choices-and-version-restores-preserve-docume.md +6 -0
  84. package/corpus/templates/content/server/lib/notion-sync.ts +80 -64
  85. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +29 -2
  86. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +86 -0
  87. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +57 -0
  88. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +11 -0
  89. package/corpus/templates/design/AGENTS.md +45 -10
  90. package/corpus/templates/design/actions/add-localhost-screens.ts +66 -13
  91. package/corpus/templates/design/actions/apply-component-prop-edit.ts +45 -36
  92. package/corpus/templates/design/actions/apply-motion-edit.ts +25 -31
  93. package/corpus/templates/design/actions/apply-shader-fill.ts +29 -42
  94. package/corpus/templates/design/actions/detach-component-instance.ts +337 -0
  95. package/corpus/templates/design/actions/export-design-as-figma-svg.ts +219 -0
  96. package/corpus/templates/design/actions/generate-design.ts +79 -12
  97. package/corpus/templates/design/actions/generate-screens.ts +57 -12
  98. package/corpus/templates/design/actions/get-figma-styles.ts +115 -0
  99. package/corpus/templates/design/actions/go-to-main-component.ts +254 -0
  100. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +4 -3
  101. package/corpus/templates/design/actions/import-design-source.ts +4 -31
  102. package/corpus/templates/design/actions/import-figma-clipboard.ts +134 -0
  103. package/corpus/templates/design/actions/import-figma-frame.ts +95 -0
  104. package/corpus/templates/design/actions/index-design-system-with-builder.ts +16 -1
  105. package/corpus/templates/design/actions/list-design-components.ts +104 -0
  106. package/corpus/templates/design/actions/list-figma-library-assets.ts +1 -24
  107. package/corpus/templates/design/actions/open-component-source.ts +2 -4
  108. package/corpus/templates/design/actions/present-design-variants.ts +412 -16
  109. package/corpus/templates/design/actions/preview-source-edit.ts +14 -1
  110. package/corpus/templates/design/actions/run-design-audit.ts +25 -3
  111. package/corpus/templates/design/actions/show-design-questions.ts +10 -0
  112. package/corpus/templates/design/actions/swap-component-instance.ts +544 -0
  113. package/corpus/templates/design/actions/take-design-screenshot.ts +45 -60
  114. package/corpus/templates/design/actions/write-local-file.ts +25 -1
  115. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +152 -0
  116. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1077 -58
  117. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +5 -5
  118. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +73 -21
  119. package/corpus/templates/design/app/components/design/EditPanel.tsx +16 -11
  120. package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +364 -0
  121. package/corpus/templates/design/app/components/design/LayersPanel.tsx +180 -53
  122. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +0 -2
  123. package/corpus/templates/design/app/components/design/MotionDock.tsx +144 -21
  124. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1152 -167
  125. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +27 -5
  126. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +23 -2
  127. package/corpus/templates/design/app/components/design/TokensPanel.tsx +63 -49
  128. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1293 -54
  129. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +7 -0
  130. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +6 -1
  131. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +23 -6
  132. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +7 -0
  133. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +5 -0
  134. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +14 -2
  135. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +37 -2
  136. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open-guard.ts +25 -0
  137. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +19 -1
  138. package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +43 -5
  139. package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +27 -2
  140. package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +10 -1
  141. package/corpus/templates/design/app/components/design/code-workbench/store.tsx +19 -2
  142. package/corpus/templates/design/app/components/design/design-canvas/annotation-snapshot.ts +265 -0
  143. package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +16 -5
  144. package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +16 -2
  145. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +7 -2
  146. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +3 -1
  147. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +78 -8
  148. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +451 -73
  149. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +12 -4
  150. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +398 -272
  151. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +41 -7
  152. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +16 -3
  153. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +24 -4
  154. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +222 -4
  155. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +171 -78
  156. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +45 -5
  157. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +299 -28
  158. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +120 -150
  159. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +53 -1
  160. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +284 -117
  161. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +81 -0
  162. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +19 -14
  163. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +7 -0
  164. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +155 -0
  165. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +268 -46
  166. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +350 -31
  167. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +13 -4
  168. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +233 -23
  169. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +72 -2
  170. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +51 -11
  171. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +84 -11
  172. package/corpus/templates/design/app/components/design/inspector/index.ts +3 -0
  173. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +552 -0
  174. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +157 -0
  175. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +53 -0
  176. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +184 -0
  177. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +403 -26
  178. package/corpus/templates/design/app/components/design/multi-screen/types.ts +20 -1
  179. package/corpus/templates/design/app/components/design/types.ts +33 -0
  180. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +59 -7
  181. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +119 -17
  182. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +275 -63
  183. package/corpus/templates/design/app/hooks/use-question-flow.ts +14 -3
  184. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +67 -10
  185. package/corpus/templates/design/app/i18n/zh-TW.ts +91 -1
  186. package/corpus/templates/design/app/i18n-data.ts +1217 -10
  187. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +577 -0
  188. package/corpus/templates/design/app/lib/agent-chat.ts +23 -0
  189. package/corpus/templates/design/app/lib/design-import.ts +24 -0
  190. package/corpus/templates/design/app/lib/figma-clipboard.ts +157 -0
  191. package/corpus/templates/design/app/lib/figma-svg-copy.ts +221 -0
  192. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +143 -30
  193. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +15 -0
  194. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +33 -0
  195. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +38 -0
  196. package/corpus/templates/design/app/pages/design-editor/overview-annotation-context.ts +157 -0
  197. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +62 -6
  198. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +245 -0
  199. package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +586 -0
  200. package/corpus/templates/design/app/pages/design-editor/screen-auto-layout.ts +169 -0
  201. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +201 -1
  202. package/corpus/templates/design/changelog/2026-07-09-added-underline-strikethrough-and-text-case-uppercase-lowerc.md +6 -0
  203. package/corpus/templates/design/changelog/2026-07-09-annotate-now-draws-across-the-all-screens-canvas-without-swi.md +6 -0
  204. package/corpus/templates/design/changelog/2026-07-09-auto-layout-children-can-now-be-dragged-into-freeform-screen.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-09-board-shapes-now-stay-visible-and-nest-correctly-across-the-.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-09-component-source-navigation-now-opens-reliably-from-the-insp.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-09-cross-screen-local-react-layer-moves-now-hand-off-exact-sour.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-09-cross-screen-moves-undo-and-redo-now-keep-both-screens-stabl.md +6 -0
  209. package/corpus/templates/design/changelog/2026-07-09-detach-a-component-instance-into-plain-editable-eleme.md +6 -0
  210. package/corpus/templates/design/changelog/2026-07-09-dragging-layers-into-auto-layout-now-respects-ignore-auto-la.md +6 -0
  211. package/corpus/templates/design/changelog/2026-07-09-drawing-annotations-sent-to-the-agent-now-include-a-rendered.md +6 -0
  212. package/corpus/templates/design/changelog/2026-07-09-editor-sidebars-now-use-figma-matched-default-widths-for-a-r.md +6 -0
  213. package/corpus/templates/design/changelog/2026-07-09-empty-frames-and-rectangles-now-expose-full-auto-layout-cont.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-09-export-any-design-screen-as-a-real-vector-svg-for-figma-with.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-09-grid-auto-layout-now-uses-true-responsive-rows-and-columns-w.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-09-hidden-and-locked-screens-now-behave-consistently-between-la.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-09-image-fills-now-preserve-valid-urls-and-layered-fit-settings.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-09-import-figma-frames-as-pixel-accurate-design-screens-via-a-s.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-09-inspect-code-now-hides-internal-styling-metadata-and-wraps-l.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-09-inspector-blur-and-shadow-effects-now-preserve-other-effects.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-09-jump-to-a-components-other-instances-from-the-inspecto.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-09-keyboard-shortcuts-are-now-discoverable-in-a-figma-style-bot.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-09-layer-selection-now-keeps-cmd-or-ctrl-toggles-and-shift-rang.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-09-layers-dropped-into-auto-layout-now-participate-correctly-in.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-09-local-live-edit-screens-now-enter-and-leave-full-view-withou.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-09-local-preview-urls-now-live-in-the-inspector-instead-of-taki.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-can-now-be-hidden-and-locked-from-layers-.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-now-keep-exact-source-provenance-and-supp.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-09-local-react-visual-editing-now-loads-reliably-and-shows-live.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-09-paste-from-figma-cmd-v-now-imports-exact-editable-nodes-when.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-09-pressing-escape-after-selecting-a-screen-now-reveals-page-pr.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-09-right-click-overlapping-or-nested-objects-to-choose-the-exac.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-09-scale-edge-stretch-and-center-constraints-now-preserve-layer.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-09-screen-rows-can-now-be-reordered-in-layers-with-matching-can.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-09-screen-selection-stays-responsive-in-designs-with-hundreds-o.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-09-shift-a-now-enables-auto-layout-on-a-selected-screen-with-fl.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-09-swap-a-component-instance-for-another-from-the-libra.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-10-drawing-annotations-now-stay-available-to-retry-when-the.md +6 -0
  239. package/corpus/templates/design/e2e/global-setup.ts +3 -6
  240. package/corpus/templates/design/e2e/helpers.ts +27 -6
  241. package/corpus/templates/design/server/lib/design-to-figma-svg.ts +1412 -0
  242. package/corpus/templates/design/server/lib/figma-clipboard-match.ts +196 -0
  243. package/corpus/templates/design/server/lib/figma-node-import.ts +311 -0
  244. package/corpus/templates/design/server/lib/figma-node-to-html.ts +1334 -0
  245. package/corpus/templates/design/server/lib/figma-paste-fallback.ts +64 -0
  246. package/corpus/templates/design/server/lib/playwright-runtime.ts +78 -0
  247. package/corpus/templates/design/server/plugins/db.ts +41 -0
  248. package/corpus/templates/design/server/source-workspace.ts +64 -0
  249. package/corpus/templates/design/shared/board-file.ts +13 -2
  250. package/corpus/templates/design/shared/canvas-math.ts +231 -33
  251. package/corpus/templates/design/shared/code-layer.ts +122 -8
  252. package/corpus/templates/design/shared/component-library.ts +161 -0
  253. package/corpus/templates/design/shared/db-conflict.ts +27 -0
  254. package/corpus/templates/design/shared/design-source-capabilities.ts +2 -1
  255. package/corpus/templates/design/shared/figma-url.ts +149 -0
  256. package/corpus/templates/design/shared/pen-path.ts +16 -7
  257. package/corpus/templates/design/shared/source-mode.ts +1 -0
  258. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +7 -19
  259. package/corpus/templates/plan/changelog/2026-07-09-canvas-panning-now-keeps-zoom-fixed-even-at-the-edges.md +6 -0
  260. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +16 -1
  261. package/corpus/templates/slides/app/context/DeckContext.tsx +311 -70
  262. package/corpus/templates/slides/changelog/2026-07-09-fixed-the-slide-rail-going-permanently-stale-during-a-long-a.md +6 -0
  263. package/dist/agent/production-agent.d.ts +79 -6
  264. package/dist/agent/production-agent.d.ts.map +1 -1
  265. package/dist/agent/production-agent.js +161 -14
  266. package/dist/agent/production-agent.js.map +1 -1
  267. package/dist/agent/run-manager.d.ts.map +1 -1
  268. package/dist/agent/run-manager.js +29 -5
  269. package/dist/agent/run-manager.js.map +1 -1
  270. package/dist/agent/run-store.d.ts +46 -0
  271. package/dist/agent/run-store.d.ts.map +1 -1
  272. package/dist/agent/run-store.js +61 -0
  273. package/dist/agent/run-store.js.map +1 -1
  274. package/dist/cli/design-connect.d.ts +6 -0
  275. package/dist/cli/design-connect.d.ts.map +1 -1
  276. package/dist/cli/design-connect.js +336 -139
  277. package/dist/cli/design-connect.js.map +1 -1
  278. package/dist/cli/skills.d.ts.map +1 -1
  279. package/dist/cli/skills.js +14 -0
  280. package/dist/cli/skills.js.map +1 -1
  281. package/dist/client/AssistantChat.d.ts +2 -0
  282. package/dist/client/AssistantChat.d.ts.map +1 -1
  283. package/dist/client/AssistantChat.js +19 -3
  284. package/dist/client/AssistantChat.js.map +1 -1
  285. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  286. package/dist/client/MultiTabAssistantChat.js +38 -18
  287. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  288. package/dist/client/agent-chat.d.ts +60 -0
  289. package/dist/client/agent-chat.d.ts.map +1 -1
  290. package/dist/client/agent-chat.js +122 -1
  291. package/dist/client/agent-chat.js.map +1 -1
  292. package/dist/client/chat/index.d.ts +1 -1
  293. package/dist/client/chat/index.d.ts.map +1 -1
  294. package/dist/client/chat/index.js +1 -1
  295. package/dist/client/chat/index.js.map +1 -1
  296. package/dist/client/extensions/EmbeddedExtension.d.ts +12 -0
  297. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  298. package/dist/client/extensions/EmbeddedExtension.js +36 -2
  299. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  300. package/dist/client/index.d.ts +1 -1
  301. package/dist/client/index.d.ts.map +1 -1
  302. package/dist/client/index.js +1 -1
  303. package/dist/client/index.js.map +1 -1
  304. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  305. package/dist/client/rich-markdown-editor/useCollabReconcile.js +18 -6
  306. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  307. package/dist/collab/awareness-store.d.ts +5 -0
  308. package/dist/collab/awareness-store.d.ts.map +1 -1
  309. package/dist/collab/awareness-store.js +19 -12
  310. package/dist/collab/awareness-store.js.map +1 -1
  311. package/dist/collab/client.d.ts.map +1 -1
  312. package/dist/collab/client.js +13 -5
  313. package/dist/collab/client.js.map +1 -1
  314. package/dist/collab/index.d.ts +1 -0
  315. package/dist/collab/index.d.ts.map +1 -1
  316. package/dist/collab/index.js +1 -0
  317. package/dist/collab/index.js.map +1 -1
  318. package/dist/collab/recent-edits.d.ts +3 -1
  319. package/dist/collab/recent-edits.d.ts.map +1 -1
  320. package/dist/collab/recent-edits.js +57 -2
  321. package/dist/collab/recent-edits.js.map +1 -1
  322. package/dist/index.browser.d.ts +1 -1
  323. package/dist/index.browser.d.ts.map +1 -1
  324. package/dist/index.browser.js +1 -1
  325. package/dist/index.browser.js.map +1 -1
  326. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  327. package/dist/server/agent-chat-plugin.js +87 -10
  328. package/dist/server/agent-chat-plugin.js.map +1 -1
  329. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  330. package/dist/server/builder-design-systems.d.ts +15 -0
  331. package/dist/server/builder-design-systems.d.ts.map +1 -1
  332. package/dist/server/builder-design-systems.js +9 -4
  333. package/dist/server/builder-design-systems.js.map +1 -1
  334. package/package.json +1 -1
@@ -0,0 +1,337 @@
1
+ /**
2
+ * detach-component-instance — Figma's "Detach instance" (⌥⌘B).
3
+ *
4
+ * There is no separate component "definition"/template markup anywhere in
5
+ * this codebase (see `shared/component-model.ts`): a component instance is
6
+ * just an ordinary element carrying a `data-agent-native-component="Name"`
7
+ * annotation (plus optional `data-agent-native-prop-*` overrides), and every
8
+ * instance of the same name is an independently-duplicated copy of HTML —
9
+ * `component_index` only stores metadata (props/variants/runtime selectors),
10
+ * never markup. Detaching an instance therefore doesn't need to "inline a
11
+ * template" the way a real component-instantiation system would: the node's
12
+ * current rendered markup already IS the fully expanded content. Detach
13
+ * severs the *component-instance linkage* by stripping the annotation
14
+ * attributes, so `is-component-instance` checks (`isComponentInstance`,
15
+ * `index-components`, `get-component-details`) stop recognizing this node —
16
+ * exactly Figma's "instance becomes a plain layer" semantics — while leaving
17
+ * position, size, layout, classes, text, and Alpine (`x-data`) behavior
18
+ * completely untouched.
19
+ *
20
+ * Persists through the same deterministic HTML-patch + collab seam as
21
+ * `apply-component-prop-edit` / `apply-visual-edit` (`writeInlineSourceFile`
22
+ * with an `expectedVersionHash` CAS guard), so it participates in the
23
+ * editor's undo/collab machinery exactly like every other HTML-mutating
24
+ * action — there is no separate undo/history log in this codebase to hook
25
+ * into (see those actions' docs).
26
+ *
27
+ * Inline/Alpine designs only; real-app sources fail closed (same posture as
28
+ * `apply-component-prop-edit`).
29
+ */
30
+
31
+ import { defineAction } from "@agent-native/core/action";
32
+ import {
33
+ agentEnterDocument,
34
+ agentLeaveDocument,
35
+ agentUpdateSelection,
36
+ } from "@agent-native/core/collab";
37
+ import {
38
+ accessFilter,
39
+ assertAccess,
40
+ resolveAccess,
41
+ } from "@agent-native/core/sharing";
42
+ import { and, eq } from "drizzle-orm";
43
+ import { z } from "zod";
44
+
45
+ import { getDb, schema } from "../server/db/index.js";
46
+ import "../server/db/index.js"; // ensure registerShareableResource runs
47
+ import {
48
+ writeInlineSourceFile,
49
+ type SourceWorkspaceFile,
50
+ } from "../server/source-workspace.js";
51
+ import { buildCodeLayerProjection } from "../shared/code-layer.js";
52
+ import type { CodeLayerSource } from "../shared/code-layer.js";
53
+ import { agentSelectionDescriptor } from "../shared/collab-selection.js";
54
+ import {
55
+ componentNameFor,
56
+ componentNodeIdMatches,
57
+ COMPONENT_NAME_ATTR,
58
+ COMPONENT_PROP_PREFIX,
59
+ } from "../shared/component-model.js";
60
+ import { designSourceTypeFromData } from "../shared/source-mode.js";
61
+ import { sourceContentHash } from "../shared/source-workspace.js";
62
+
63
+ // ─── Pure transform ────────────────────────────────────────────────────────
64
+
65
+ /**
66
+ * Strip the `data-agent-native-component` annotation and every
67
+ * `data-agent-native-prop-*` override attribute from a node's opening tag,
68
+ * using its source span. Pure — no DB / IO — so it can be unit tested
69
+ * directly.
70
+ *
71
+ * Leaves everything else (classes, style, text, `x-data`, `id`,
72
+ * `data-agent-native-node-id`, …) untouched: the node keeps its current
73
+ * rendered appearance and behavior, it simply stops being recognized as a
74
+ * component instance.
75
+ */
76
+ export function stripComponentAnnotations(
77
+ html: string,
78
+ source: { openStart: number; openEnd: number } | null | undefined,
79
+ ): { content: string; changed: boolean; removedAttributes: string[] } {
80
+ if (!source) {
81
+ return { content: html, changed: false, removedAttributes: [] };
82
+ }
83
+
84
+ const openTag = html.slice(source.openStart, source.openEnd);
85
+ const removed: string[] = [];
86
+ let newOpenTag = openTag;
87
+
88
+ const componentAttrRe = new RegExp(
89
+ `\\s+${COMPONENT_NAME_ATTR}\\s*=\\s*(?:"[^"]*"|'[^']*'|[^\\s>"']+)`,
90
+ "i",
91
+ );
92
+ if (componentAttrRe.test(newOpenTag)) {
93
+ removed.push(COMPONENT_NAME_ATTR);
94
+ newOpenTag = newOpenTag.replace(componentAttrRe, "");
95
+ }
96
+
97
+ const propAttrRe = new RegExp(
98
+ `\\s+${COMPONENT_PROP_PREFIX}[a-z0-9-]+\\s*=\\s*(?:"[^"]*"|'[^']*'|[^\\s>"']+)`,
99
+ "gi",
100
+ );
101
+ newOpenTag = newOpenTag.replace(propAttrRe, (match) => {
102
+ removed.push(match.trim().split("=")[0] ?? match.trim());
103
+ return "";
104
+ });
105
+
106
+ if (newOpenTag === openTag) {
107
+ return { content: html, changed: false, removedAttributes: [] };
108
+ }
109
+
110
+ return {
111
+ content:
112
+ html.slice(0, source.openStart) + newOpenTag + html.slice(source.openEnd),
113
+ changed: true,
114
+ removedAttributes: removed,
115
+ };
116
+ }
117
+
118
+ // ─── Persistence ────────────────────────────────────────────────────────────
119
+
120
+ async function persistEdit(file: {
121
+ id: string;
122
+ designId: string;
123
+ filename: string;
124
+ content: string;
125
+ expectedVersionHash: string;
126
+ }): Promise<string> {
127
+ await assertAccess("design", file.designId, "editor");
128
+
129
+ agentEnterDocument(file.id);
130
+ try {
131
+ const workspaceFile: SourceWorkspaceFile = {
132
+ id: file.id,
133
+ designId: file.designId,
134
+ filename: file.filename,
135
+ fileType: "html",
136
+ content: file.content,
137
+ createdAt: null,
138
+ updatedAt: null,
139
+ };
140
+
141
+ const result = await writeInlineSourceFile({
142
+ designId: file.designId,
143
+ file: workspaceFile,
144
+ content: file.content,
145
+ expectedVersionHash: file.expectedVersionHash,
146
+ });
147
+
148
+ return result.updatedAt;
149
+ } finally {
150
+ agentLeaveDocument(file.id);
151
+ }
152
+ }
153
+
154
+ // ─── Action ───────────────────────────────────────────────────────────────────
155
+
156
+ export default defineAction({
157
+ description:
158
+ "Detach a component instance (Figma's ⌥⌘B): strip the " +
159
+ "data-agent-native-component annotation and its data-agent-native-prop-* " +
160
+ "overrides from the selected instance root so it becomes a plain, " +
161
+ "unlinked element. Position, size, classes, text, and behavior are " +
162
+ "unchanged — only the component-instance linkage is removed. " +
163
+ "Inline/Alpine designs only.",
164
+ schema: z.object({
165
+ designId: z.string().describe("Design project ID"),
166
+ nodeId: z
167
+ .string()
168
+ .describe("data-agent-native-node-id of the component instance root"),
169
+ fileId: z
170
+ .string()
171
+ .optional()
172
+ .describe("Design file id; defaults to index.html"),
173
+ source: z
174
+ .object({
175
+ currentContent: z
176
+ .string()
177
+ .optional()
178
+ .describe("Latest editor HTML snapshot, when available."),
179
+ revision: z
180
+ .string()
181
+ .optional()
182
+ .describe(
183
+ "design_files.updatedAt value the currentContent is based on.",
184
+ ),
185
+ })
186
+ .optional(),
187
+ }),
188
+ run: async ({ designId, nodeId, fileId, source }) => {
189
+ const access = await resolveAccess("design", designId);
190
+ if (!access) throw new Error("Design not found");
191
+
192
+ const rawData = (access.resource as { data?: unknown }).data;
193
+ const sourceType = designSourceTypeFromData(rawData);
194
+
195
+ if (sourceType !== "inline") {
196
+ return {
197
+ designId,
198
+ nodeId,
199
+ sourceType,
200
+ detached: false,
201
+ ctaRequired: true,
202
+ ctaMessage:
203
+ "Detach instance requires a dedicated consented, version-guarded " +
204
+ "compiled-source transform for real-app sources. Not yet available.",
205
+ };
206
+ }
207
+
208
+ await assertAccess("design", designId, "editor");
209
+ const db = getDb();
210
+
211
+ const conditions = [
212
+ accessFilter(schema.designs, schema.designShares),
213
+ eq(schema.designFiles.designId, designId),
214
+ fileId
215
+ ? eq(schema.designFiles.id, fileId)
216
+ : eq(schema.designFiles.filename, "index.html"),
217
+ ];
218
+
219
+ const [file] = await db
220
+ .select({
221
+ id: schema.designFiles.id,
222
+ designId: schema.designFiles.designId,
223
+ filename: schema.designFiles.filename,
224
+ content: schema.designFiles.content,
225
+ updatedAt: schema.designFiles.updatedAt,
226
+ })
227
+ .from(schema.designFiles)
228
+ .innerJoin(
229
+ schema.designs,
230
+ eq(schema.designFiles.designId, schema.designs.id),
231
+ )
232
+ .where(and(...conditions))
233
+ .limit(1);
234
+
235
+ if (!file) throw new Error("Design HTML file not found.");
236
+
237
+ if (
238
+ source?.currentContent &&
239
+ source.revision &&
240
+ file.updatedAt &&
241
+ source.revision !== file.updatedAt
242
+ ) {
243
+ return {
244
+ designId,
245
+ nodeId,
246
+ detached: false,
247
+ conflict: true,
248
+ fileId: file.id,
249
+ filename: file.filename,
250
+ error:
251
+ "This file changed since this detach was prepared. Refresh the editor and try again.",
252
+ };
253
+ }
254
+
255
+ const html =
256
+ typeof source?.currentContent === "string"
257
+ ? source.currentContent
258
+ : (file.content ?? "");
259
+ const baseVersionHash = sourceContentHash(html);
260
+
261
+ const codeLayerSource: CodeLayerSource = {
262
+ kind: "design-file",
263
+ designId: file.designId,
264
+ fileId: file.id,
265
+ filename: file.filename,
266
+ };
267
+
268
+ const projection = buildCodeLayerProjection(html, {
269
+ source: codeLayerSource,
270
+ });
271
+
272
+ const node = projection.nodes.find((n) =>
273
+ componentNodeIdMatches(n, nodeId),
274
+ );
275
+ if (!node) {
276
+ throw new Error(
277
+ `Node "${nodeId}" not found. Run get-code-layer-projection to list current ids.`,
278
+ );
279
+ }
280
+
281
+ const componentName = componentNameFor(node);
282
+ if (!componentName) {
283
+ throw new Error(
284
+ `Node "${nodeId}" is not a component root (no data-agent-native-component attribute) — nothing to detach.`,
285
+ );
286
+ }
287
+
288
+ const {
289
+ content: patchedContent,
290
+ changed,
291
+ removedAttributes,
292
+ } = stripComponentAnnotations(html, node.source);
293
+
294
+ if (!changed) {
295
+ return {
296
+ designId,
297
+ nodeId,
298
+ componentName,
299
+ detached: false,
300
+ fileId: file.id,
301
+ filename: file.filename,
302
+ note: "No component annotation attributes found on this node's open tag.",
303
+ };
304
+ }
305
+
306
+ const updatedAt = await persistEdit({
307
+ id: file.id,
308
+ designId: file.designId,
309
+ filename: file.filename,
310
+ content: patchedContent,
311
+ expectedVersionHash: baseVersionHash,
312
+ });
313
+
314
+ agentUpdateSelection(file.id, {
315
+ selection: agentSelectionDescriptor(
316
+ { nodeId, selector: node.selector },
317
+ "Detaching instance",
318
+ ),
319
+ nodeId,
320
+ editingFile: file.filename,
321
+ designId: file.designId,
322
+ });
323
+
324
+ return {
325
+ designId,
326
+ nodeId,
327
+ componentName,
328
+ detached: true,
329
+ removedAttributes,
330
+ fileId: file.id,
331
+ filename: file.filename,
332
+ updatedAt,
333
+ content: patchedContent,
334
+ note: `Detached from component "${componentName}". This node is now a plain element.`,
335
+ };
336
+ },
337
+ });
@@ -0,0 +1,219 @@
1
+ /**
2
+ * export-design-as-figma-svg — export a design screen (or a selected
3
+ * subtree) as a genuinely VECTOR SVG document that Figma imports as
4
+ * editable layers (rect/path/gradients/filters stay editable; `<text>` is
5
+ * vectorized to outline paths on import — see the returned report's
6
+ * `vectorizedTextCaveat`). This is a different artifact from `export-svg`,
7
+ * which wraps the standalone HTML in a `foreignObject` for the editor's own
8
+ * "Download SVG" parity — Figma cannot import that as vectors at all, it
9
+ * stays an opaque embedded HTML blob.
10
+ *
11
+ * Resolution mirrors `take-design-screenshot.ts`: a single HTML screen
12
+ * (`fileId`, or `designId` + `filename` defaulting to `index.html`), using
13
+ * live collab content when the screen is actively being edited. `nodeId`
14
+ * additionally scopes the export to one selected element's subtree via its
15
+ * `data-agent-native-node-id` — the same attribute the editor stamps on
16
+ * selectable layers.
17
+ */
18
+
19
+ import { defineAction } from "@agent-native/core";
20
+ import { getText, hasCollabState } from "@agent-native/core/collab";
21
+ import { accessFilter } from "@agent-native/core/sharing";
22
+ import { and, eq } from "drizzle-orm";
23
+ import { z } from "zod";
24
+
25
+ import { getDb, schema } from "../server/db/index.js";
26
+ import { trySaveExportFile } from "../server/lib/design-export.js";
27
+ import {
28
+ renderDesignToFigmaSvg,
29
+ safeFigmaSvgFilename,
30
+ } from "../server/lib/design-to-figma-svg.js";
31
+ import { isMissingBrowserError } from "../server/lib/playwright-runtime.js";
32
+ import "../server/db/index.js"; // ensure registerShareableResource runs
33
+
34
+ async function liveContent(
35
+ fileId: string,
36
+ storedContent: string,
37
+ ): Promise<string> {
38
+ try {
39
+ if (await hasCollabState(fileId)) {
40
+ const live = await getText(fileId, "content");
41
+ if (typeof live === "string") return live;
42
+ }
43
+ } catch {
44
+ // SQL content is the deterministic fallback.
45
+ }
46
+ return storedContent;
47
+ }
48
+
49
+ /** Model-actionable message when no headless Chromium binary is available. */
50
+ export function chromiumUnavailableReason(err: unknown): string {
51
+ const detail = err instanceof Error ? err.message : String(err);
52
+ return (
53
+ "A headless Chromium browser is not available in this environment, so the " +
54
+ "vector SVG export cannot run here (this is expected in hosted/serverless " +
55
+ "deploys, which do not bundle a Chromium binary). Fall back to `export-svg` " +
56
+ "(a foreignObject-wrapped HTML snapshot — not Figma-importable as vectors, " +
57
+ "but still a usable static preview) or `export-html`. " +
58
+ `(${detail})`
59
+ );
60
+ }
61
+
62
+ export default defineAction({
63
+ description:
64
+ "Export a design screen (or a selected element's subtree via nodeId) as a " +
65
+ "genuinely vector SVG document — real <rect>/<path>/<text>/<image> markup " +
66
+ "with <linearGradient>/<radialGradient>/<filter> defs, which Figma imports " +
67
+ "as normal EDITABLE layers (unlike `export-svg`'s foreignObject wrapper, " +
68
+ "which Figma cannot import as vectors). Returns the SVG string plus an " +
69
+ "export report classifying each element as vectorized, approximated, " +
70
+ "rasterized, or omitted. Note: Figma converts all imported SVG <text> to " +
71
+ "outlined vector paths on paste/drag-import — geometry stays pixel-exact " +
72
+ "but the text is no longer live/editable type in Figma; see the report's " +
73
+ "`vectorizedTextCaveat`.",
74
+ schema: z.object({
75
+ designId: z
76
+ .string()
77
+ .optional()
78
+ .describe("Design project id. Required unless fileId is provided."),
79
+ fileId: z
80
+ .string()
81
+ .optional()
82
+ .describe(
83
+ "Specific design_files.id to export. Takes priority over designId/filename.",
84
+ ),
85
+ filename: z
86
+ .string()
87
+ .optional()
88
+ .default("index.html")
89
+ .describe(
90
+ "Filename to export when fileId is not provided. Defaults to index.html.",
91
+ ),
92
+ nodeId: z
93
+ .string()
94
+ .optional()
95
+ .describe(
96
+ "Scope the export to one element's subtree via its data-agent-native-node-id, " +
97
+ "instead of the whole screen.",
98
+ ),
99
+ embedImages: z
100
+ .boolean()
101
+ .optional()
102
+ .default(true)
103
+ .describe(
104
+ "Fetch and inline http(s) image sources/background-images as data: URIs, so the " +
105
+ "SVG is self-contained for clipboard paste. Set false to keep absolute URLs.",
106
+ ),
107
+ width: z.coerce
108
+ .number()
109
+ .int()
110
+ .positive()
111
+ .optional()
112
+ .describe(
113
+ "Render viewport width in px. Defaults to the screen's natural width or 1440.",
114
+ ),
115
+ height: z.coerce
116
+ .number()
117
+ .int()
118
+ .positive()
119
+ .optional()
120
+ .describe(
121
+ "Render viewport height in px. Defaults to the screen's natural height or 1200.",
122
+ ),
123
+ }),
124
+ readOnly: true,
125
+ http: { method: "POST" },
126
+ run: async ({
127
+ designId,
128
+ fileId,
129
+ filename,
130
+ nodeId,
131
+ embedImages,
132
+ width,
133
+ height,
134
+ }) => {
135
+ if (!designId && !fileId) {
136
+ throw new Error("designId or fileId is required.");
137
+ }
138
+
139
+ const db = getDb();
140
+ const conditions = [
141
+ accessFilter(schema.designs, schema.designShares),
142
+ fileId
143
+ ? eq(schema.designFiles.id, fileId)
144
+ : eq(schema.designFiles.designId, designId ?? ""),
145
+ ];
146
+ if (!fileId) {
147
+ conditions.push(
148
+ eq(schema.designFiles.filename, filename ?? "index.html"),
149
+ );
150
+ }
151
+
152
+ const [file] = await db
153
+ .select({
154
+ id: schema.designFiles.id,
155
+ designId: schema.designFiles.designId,
156
+ filename: schema.designFiles.filename,
157
+ fileType: schema.designFiles.fileType,
158
+ content: schema.designFiles.content,
159
+ })
160
+ .from(schema.designFiles)
161
+ .innerJoin(
162
+ schema.designs,
163
+ eq(schema.designFiles.designId, schema.designs.id),
164
+ )
165
+ .where(and(...conditions))
166
+ .limit(1);
167
+
168
+ if (!file) {
169
+ const err = new Error("Design file not found") as Error & {
170
+ statusCode: number;
171
+ };
172
+ err.statusCode = 404;
173
+ throw err;
174
+ }
175
+ if (file.fileType !== "html") {
176
+ throw new Error(
177
+ `export-design-as-figma-svg only supports HTML files (got "${file.fileType}").`,
178
+ );
179
+ }
180
+
181
+ const [designRow] = await db
182
+ .select({ title: schema.designs.title })
183
+ .from(schema.designs)
184
+ .where(eq(schema.designs.id, file.designId))
185
+ .limit(1);
186
+
187
+ const html = await liveContent(file.id, file.content ?? "");
188
+
189
+ let result: Awaited<ReturnType<typeof renderDesignToFigmaSvg>>;
190
+ try {
191
+ result = await renderDesignToFigmaSvg({
192
+ html,
193
+ width: width ?? 1440,
194
+ height: height ?? 1200,
195
+ title: designRow?.title ?? file.filename,
196
+ rootSelector: nodeId ? `[data-agent-native-node-id="${nodeId}"]` : null,
197
+ embedImages: embedImages ?? true,
198
+ });
199
+ } catch (err) {
200
+ if (isMissingBrowserError(err)) {
201
+ return { ok: false, reason: chromiumUnavailableReason(err) };
202
+ }
203
+ throw err;
204
+ }
205
+
206
+ const filenameOut = safeFigmaSvgFilename(designRow?.title ?? file.filename);
207
+ const saveResult = await trySaveExportFile(filenameOut, result.svg);
208
+
209
+ return {
210
+ ok: true,
211
+ designId: file.designId,
212
+ fileId: file.id,
213
+ filename: filenameOut,
214
+ svg: result.svg,
215
+ report: result.report,
216
+ ...saveResult,
217
+ };
218
+ },
219
+ });
@@ -57,23 +57,59 @@ function jsonValuesEqual(left: unknown, right: unknown): boolean {
57
57
  return JSON.stringify(left) === JSON.stringify(right);
58
58
  }
59
59
 
60
+ // Same-process mutex guarding the generation-session read-modify-write below.
61
+ // generate-screens' own tool description explicitly recommends fanning out
62
+ // parallel generate-design calls per returned frame ("fan out calls to
63
+ // generate-design for each returned frame"). Without this lock, two
64
+ // concurrent calls for the same designId both read the same pre-update
65
+ // session, each only marks its own frame done, and whichever writeAppState
66
+ // lands second silently discards the first call's frame-done update —
67
+ // application state has no CAS/versioning primitive (unlike designs.data's
68
+ // mutateDesignData), so a plain read-then-write here is a classic lost-update
69
+ // race. This mirrors the same-process serialization
70
+ // server/lib/design-data-mutation.ts uses (withDesignDataLock) for the
71
+ // designs.data column. Cross-process races remain (no CAS primitive exists
72
+ // for application state), but same-process fan-out from one agent turn is
73
+ // the realistic, explicitly-encouraged case this closes.
74
+ const generationSessionLocks = new Map<string, Promise<unknown>>();
75
+
76
+ function withGenerationSessionLock<T>(
77
+ designId: string,
78
+ callback: () => Promise<T>,
79
+ ): Promise<T> {
80
+ const previous = generationSessionLocks.get(designId) ?? Promise.resolve();
81
+ const next = previous.then(callback, callback);
82
+ generationSessionLocks.set(designId, next);
83
+ const cleanup = () => {
84
+ if (generationSessionLocks.get(designId) === next) {
85
+ generationSessionLocks.delete(designId);
86
+ }
87
+ };
88
+ next.then(cleanup, cleanup);
89
+ return next;
90
+ }
91
+
60
92
  async function updateGenerationSessionForSavedFiles(
61
93
  designId: string,
62
94
  savedFilenames: string[],
63
95
  ) {
64
- const key = designGenerationSessionKey(designId);
65
- const rawSession = await readAppState(key).catch(() => null);
66
- if (!rawSession || typeof rawSession !== "object") return;
67
- const session = rawSession as unknown as DesignGenerationSession;
68
- if (session.designId !== designId || !Array.isArray(session.frames)) return;
69
-
70
- const nextSession = updateGenerationSessionWithSavedFiles(
71
- session,
72
- savedFilenames,
73
- );
74
- if (nextSession === session) return;
96
+ await withGenerationSessionLock(designId, async () => {
97
+ const key = designGenerationSessionKey(designId);
98
+ const rawSession = await readAppState(key).catch(() => null);
99
+ if (!rawSession || typeof rawSession !== "object") return;
100
+ const session = rawSession as unknown as DesignGenerationSession;
101
+ if (session.designId !== designId || !Array.isArray(session.frames)) {
102
+ return;
103
+ }
104
+
105
+ const nextSession = updateGenerationSessionWithSavedFiles(
106
+ session,
107
+ savedFilenames,
108
+ );
109
+ if (nextSession === session) return;
75
110
 
76
- await writeAppState(key, nextSession as unknown as Record<string, unknown>);
111
+ await writeAppState(key, nextSession as unknown as Record<string, unknown>);
112
+ });
77
113
  }
78
114
 
79
115
  const generateDesignAgentParameters = {
@@ -228,6 +264,37 @@ const generateDesignAction = defineAction({
228
264
  message: "canvasFrames entries require fileId or filename",
229
265
  }),
230
266
  )
267
+ // Reject two placements for the same target in one call. Without
268
+ // this, mergeCanvasFramePlacements folds both entries into a single
269
+ // canvasFrames[fileId] value (last one wins), but the mutateDesignData
270
+ // isApplied check below verifies EVERY placedFrames entry against
271
+ // that single folded value — so the earlier, now-overwritten entry
272
+ // always fails the equality check. Since the mutate callback is
273
+ // deterministic, every retry recomputes the identical mismatch, so
274
+ // the whole action always fails with a "concurrent write conflicts"
275
+ // error after burning through all retries, even though nothing else
276
+ // was actually writing to the design.
277
+ .superRefine((frames, ctx) => {
278
+ const seen = new Map<string, number>();
279
+ frames.forEach((frame, index) => {
280
+ const key = frame.fileId
281
+ ? `id:${frame.fileId}`
282
+ : `name:${frame.filename}`;
283
+ const firstIndex = seen.get(key);
284
+ if (firstIndex !== undefined) {
285
+ ctx.addIssue({
286
+ code: z.ZodIssueCode.custom,
287
+ path: [index],
288
+ message:
289
+ `canvasFrames entry ${index} duplicates the target already placed ` +
290
+ `at index ${firstIndex} (${frame.fileId ? `fileId ${frame.fileId}` : `filename ${frame.filename}`}). ` +
291
+ "Pass exactly one placement per screen.",
292
+ });
293
+ return;
294
+ }
295
+ seen.set(key, index);
296
+ });
297
+ })
231
298
  .optional(),
232
299
  )
233
300
  .optional()