@agent-native/core 0.92.10 → 0.92.12

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 (354) 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 +214 -17
  5. package/corpus/core/src/agent/run-manager.ts +72 -14
  6. package/corpus/core/src/agent/run-store.ts +152 -27
  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-adapter.ts +56 -12
  12. package/corpus/core/src/client/agent-chat.ts +165 -1
  13. package/corpus/core/src/client/chat/index.ts +9 -1
  14. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +38 -2
  15. package/corpus/core/src/client/index.ts +5 -0
  16. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +18 -7
  17. package/corpus/core/src/collab/awareness-store.ts +23 -12
  18. package/corpus/core/src/collab/client.ts +16 -5
  19. package/corpus/core/src/collab/index.ts +4 -0
  20. package/corpus/core/src/collab/recent-edits.ts +65 -2
  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 +75 -9
  33. package/corpus/templates/clips/app/components/player/video-player.tsx +17 -12
  34. package/corpus/templates/clips/app/hooks/use-player-shortcuts.ts +1 -0
  35. package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
  36. package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
  37. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  38. package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
  39. package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
  40. package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
  41. package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
  42. package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
  43. package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
  44. package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
  45. package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
  46. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -37
  47. package/corpus/templates/clips/app/routes/record.tsx +17 -4
  48. package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
  49. package/corpus/templates/clips/changelog/2026-07-09-desktop-popover-sits-closer-to-the-menu-bar.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-07-09-desktop-recording-optimization-progress-stays-visible-until-the-clip-opens.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-07-09-fixed-large-video-file-uploads-failing-before-their-resumabl.md +6 -0
  52. package/corpus/templates/clips/changelog/2026-07-09-fixed-new-video-uploads-defaulting-to-private.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-07-09-long-recordings-now-get-enough-time-to-download-their-media-.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-09-meeting-notes-no-longer-lowers-your-microphone-volume-for-ot.md +6 -0
  55. package/corpus/templates/clips/changelog/2026-07-09-on-mobile-tapping-a-playing-clip-reveals-playback-controls-w.md +6 -0
  56. package/corpus/templates/clips/changelog/2026-07-09-regenerate-any-clip-transcript-from-its-saved-recording-or-r.md +6 -0
  57. package/corpus/templates/clips/changelog/2026-07-09-slack-previews-now-show-the-clip-length-alongside-its-descri.md +6 -0
  58. package/corpus/templates/clips/changelog/2026-07-10-fixed-copying-agent-responses-and-transcript-text-from-recor.md +6 -0
  59. package/corpus/templates/clips/desktop/src/app.tsx +12 -2
  60. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +8 -1
  61. package/corpus/templates/clips/desktop/src/lib/recorder.ts +8 -5
  62. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +5 -5
  63. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +3 -0
  64. package/corpus/templates/clips/desktop/src/styles.css +6 -3
  65. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +29 -11
  66. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
  67. package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +103 -21
  68. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +117 -39
  69. package/corpus/templates/clips/server/lib/slack-unfurls.ts +20 -1
  70. package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +10 -4
  71. package/corpus/templates/clips/shared/recording-core.ts +12 -0
  72. package/corpus/templates/content/AGENTS.md +7 -6
  73. package/corpus/templates/content/actions/_builder-cms-read-client.ts +82 -7
  74. package/corpus/templates/content/actions/_database-source-utils.ts +149 -30
  75. package/corpus/templates/content/actions/_document-flush.ts +132 -17
  76. package/corpus/templates/content/actions/_notion-action-utils.ts +15 -0
  77. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +4 -7
  78. package/corpus/templates/content/actions/attach-content-database-source.ts +41 -17
  79. package/corpus/templates/content/actions/change-content-database-source-role.ts +46 -8
  80. package/corpus/templates/content/actions/create-and-link-notion-page.ts +2 -0
  81. package/corpus/templates/content/actions/link-notion-page.ts +2 -0
  82. package/corpus/templates/content/actions/pull-document.ts +5 -7
  83. package/corpus/templates/content/actions/pull-notion-page.ts +2 -0
  84. package/corpus/templates/content/actions/push-notion-page.ts +10 -0
  85. package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +2 -0
  86. package/corpus/templates/content/actions/suggest-source-join-key.ts +8 -1
  87. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +88 -23
  88. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +32 -2
  89. package/corpus/templates/content/app/hooks/use-notion.ts +6 -6
  90. package/corpus/templates/content/app/i18n/zh-TW.ts +1 -0
  91. package/corpus/templates/content/app/i18n-data.ts +18 -0
  92. package/corpus/templates/content/changelog/2026-07-09-builder-source-refreshes-now-keep-mapped-fields-such-as-topi.md +6 -0
  93. package/corpus/templates/content/changelog/2026-07-09-notion-conflict-choices-and-version-restores-preserve-docume.md +6 -0
  94. package/corpus/templates/content/server/lib/notion-sync.ts +80 -64
  95. package/corpus/templates/content/shared/api.ts +1 -0
  96. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +29 -2
  97. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +86 -0
  98. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +57 -0
  99. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +11 -0
  100. package/corpus/templates/design/AGENTS.md +45 -10
  101. package/corpus/templates/design/actions/add-localhost-screens.ts +66 -13
  102. package/corpus/templates/design/actions/apply-component-prop-edit.ts +45 -36
  103. package/corpus/templates/design/actions/apply-motion-edit.ts +25 -31
  104. package/corpus/templates/design/actions/apply-shader-fill.ts +29 -42
  105. package/corpus/templates/design/actions/detach-component-instance.ts +337 -0
  106. package/corpus/templates/design/actions/export-design-as-figma-svg.ts +219 -0
  107. package/corpus/templates/design/actions/generate-design.ts +79 -12
  108. package/corpus/templates/design/actions/generate-screens.ts +57 -12
  109. package/corpus/templates/design/actions/get-figma-styles.ts +115 -0
  110. package/corpus/templates/design/actions/go-to-main-component.ts +254 -0
  111. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +4 -3
  112. package/corpus/templates/design/actions/import-design-source.ts +4 -31
  113. package/corpus/templates/design/actions/import-figma-clipboard.ts +134 -0
  114. package/corpus/templates/design/actions/import-figma-frame.ts +95 -0
  115. package/corpus/templates/design/actions/index-design-system-with-builder.ts +16 -1
  116. package/corpus/templates/design/actions/list-design-components.ts +104 -0
  117. package/corpus/templates/design/actions/list-figma-library-assets.ts +1 -24
  118. package/corpus/templates/design/actions/open-component-source.ts +2 -4
  119. package/corpus/templates/design/actions/present-design-variants.ts +412 -16
  120. package/corpus/templates/design/actions/preview-source-edit.ts +14 -1
  121. package/corpus/templates/design/actions/run-design-audit.ts +25 -3
  122. package/corpus/templates/design/actions/show-design-questions.ts +10 -0
  123. package/corpus/templates/design/actions/swap-component-instance.ts +544 -0
  124. package/corpus/templates/design/actions/take-design-screenshot.ts +45 -60
  125. package/corpus/templates/design/actions/write-local-file.ts +25 -1
  126. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +152 -0
  127. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1077 -58
  128. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +5 -5
  129. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +73 -21
  130. package/corpus/templates/design/app/components/design/EditPanel.tsx +16 -11
  131. package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +364 -0
  132. package/corpus/templates/design/app/components/design/LayersPanel.tsx +180 -53
  133. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +0 -2
  134. package/corpus/templates/design/app/components/design/MotionDock.tsx +144 -21
  135. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1152 -167
  136. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +27 -5
  137. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +23 -2
  138. package/corpus/templates/design/app/components/design/TokensPanel.tsx +63 -49
  139. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1293 -54
  140. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +7 -0
  141. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +6 -1
  142. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +23 -6
  143. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +7 -0
  144. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +5 -0
  145. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +14 -2
  146. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +37 -2
  147. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open-guard.ts +25 -0
  148. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +19 -1
  149. package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +43 -5
  150. package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +27 -2
  151. package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +10 -1
  152. package/corpus/templates/design/app/components/design/code-workbench/store.tsx +19 -2
  153. package/corpus/templates/design/app/components/design/design-canvas/annotation-snapshot.ts +265 -0
  154. package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +16 -5
  155. package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +16 -2
  156. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +7 -2
  157. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +3 -1
  158. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +78 -8
  159. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +451 -73
  160. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +12 -4
  161. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +398 -272
  162. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +41 -7
  163. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +16 -3
  164. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +24 -4
  165. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +222 -4
  166. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +171 -78
  167. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +45 -5
  168. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +299 -28
  169. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +120 -150
  170. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +53 -1
  171. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +284 -117
  172. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +81 -0
  173. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +19 -14
  174. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +7 -0
  175. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +155 -0
  176. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +268 -46
  177. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +350 -31
  178. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +13 -4
  179. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +233 -23
  180. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +72 -2
  181. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +51 -11
  182. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +84 -11
  183. package/corpus/templates/design/app/components/design/inspector/index.ts +3 -0
  184. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +552 -0
  185. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +157 -0
  186. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +53 -0
  187. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +184 -0
  188. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +403 -26
  189. package/corpus/templates/design/app/components/design/multi-screen/types.ts +20 -1
  190. package/corpus/templates/design/app/components/design/types.ts +33 -0
  191. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +59 -7
  192. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +119 -17
  193. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +275 -63
  194. package/corpus/templates/design/app/hooks/use-question-flow.ts +14 -3
  195. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +67 -10
  196. package/corpus/templates/design/app/i18n/zh-TW.ts +91 -1
  197. package/corpus/templates/design/app/i18n-data.ts +1217 -10
  198. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +577 -0
  199. package/corpus/templates/design/app/lib/agent-chat.ts +23 -0
  200. package/corpus/templates/design/app/lib/design-import.ts +24 -0
  201. package/corpus/templates/design/app/lib/figma-clipboard.ts +157 -0
  202. package/corpus/templates/design/app/lib/figma-svg-copy.ts +221 -0
  203. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +143 -30
  204. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +15 -0
  205. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +33 -0
  206. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +38 -0
  207. package/corpus/templates/design/app/pages/design-editor/overview-annotation-context.ts +157 -0
  208. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +62 -6
  209. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +245 -0
  210. package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +586 -0
  211. package/corpus/templates/design/app/pages/design-editor/screen-auto-layout.ts +169 -0
  212. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +201 -1
  213. package/corpus/templates/design/changelog/2026-07-09-added-underline-strikethrough-and-text-case-uppercase-lowerc.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-09-annotate-now-draws-across-the-all-screens-canvas-without-swi.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-09-auto-layout-children-can-now-be-dragged-into-freeform-screen.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-09-board-shapes-now-stay-visible-and-nest-correctly-across-the-.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-09-component-source-navigation-now-opens-reliably-from-the-insp.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-09-cross-screen-local-react-layer-moves-now-hand-off-exact-sour.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-09-cross-screen-moves-undo-and-redo-now-keep-both-screens-stabl.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-09-detach-a-component-instance-into-plain-editable-eleme.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-09-dragging-layers-into-auto-layout-now-respects-ignore-auto-la.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-09-drawing-annotations-sent-to-the-agent-now-include-a-rendered.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-09-editor-sidebars-now-use-figma-matched-default-widths-for-a-r.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-09-empty-frames-and-rectangles-now-expose-full-auto-layout-cont.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-09-export-any-design-screen-as-a-real-vector-svg-for-figma-with.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-09-grid-auto-layout-now-uses-true-responsive-rows-and-columns-w.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-09-hidden-and-locked-screens-now-behave-consistently-between-la.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-09-image-fills-now-preserve-valid-urls-and-layered-fit-settings.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-09-import-figma-frames-as-pixel-accurate-design-screens-via-a-s.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-09-inspect-code-now-hides-internal-styling-metadata-and-wraps-l.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-09-inspector-blur-and-shadow-effects-now-preserve-other-effects.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-09-jump-to-a-components-other-instances-from-the-inspecto.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-09-keyboard-shortcuts-are-now-discoverable-in-a-figma-style-bot.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-09-layer-selection-now-keeps-cmd-or-ctrl-toggles-and-shift-rang.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-09-layers-dropped-into-auto-layout-now-participate-correctly-in.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-09-local-live-edit-screens-now-enter-and-leave-full-view-withou.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-09-local-preview-urls-now-live-in-the-inspector-instead-of-taki.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-can-now-be-hidden-and-locked-from-layers-.md +6 -0
  239. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-now-keep-exact-source-provenance-and-supp.md +6 -0
  240. package/corpus/templates/design/changelog/2026-07-09-local-react-visual-editing-now-loads-reliably-and-shows-live.md +6 -0
  241. package/corpus/templates/design/changelog/2026-07-09-paste-from-figma-cmd-v-now-imports-exact-editable-nodes-when.md +6 -0
  242. package/corpus/templates/design/changelog/2026-07-09-pressing-escape-after-selecting-a-screen-now-reveals-page-pr.md +6 -0
  243. package/corpus/templates/design/changelog/2026-07-09-right-click-overlapping-or-nested-objects-to-choose-the-exac.md +6 -0
  244. package/corpus/templates/design/changelog/2026-07-09-scale-edge-stretch-and-center-constraints-now-preserve-layer.md +6 -0
  245. package/corpus/templates/design/changelog/2026-07-09-screen-rows-can-now-be-reordered-in-layers-with-matching-can.md +6 -0
  246. package/corpus/templates/design/changelog/2026-07-09-screen-selection-stays-responsive-in-designs-with-hundreds-o.md +6 -0
  247. package/corpus/templates/design/changelog/2026-07-09-shift-a-now-enables-auto-layout-on-a-selected-screen-with-fl.md +6 -0
  248. package/corpus/templates/design/changelog/2026-07-09-swap-a-component-instance-for-another-from-the-libra.md +6 -0
  249. package/corpus/templates/design/changelog/2026-07-10-drawing-annotations-now-stay-available-to-retry-when-the.md +6 -0
  250. package/corpus/templates/design/e2e/global-setup.ts +3 -6
  251. package/corpus/templates/design/e2e/helpers.ts +27 -6
  252. package/corpus/templates/design/server/lib/design-to-figma-svg.ts +1412 -0
  253. package/corpus/templates/design/server/lib/figma-clipboard-match.ts +196 -0
  254. package/corpus/templates/design/server/lib/figma-node-import.ts +311 -0
  255. package/corpus/templates/design/server/lib/figma-node-to-html.ts +1334 -0
  256. package/corpus/templates/design/server/lib/figma-paste-fallback.ts +64 -0
  257. package/corpus/templates/design/server/lib/playwright-runtime.ts +78 -0
  258. package/corpus/templates/design/server/plugins/db.ts +41 -0
  259. package/corpus/templates/design/server/source-workspace.ts +64 -0
  260. package/corpus/templates/design/shared/board-file.ts +13 -2
  261. package/corpus/templates/design/shared/canvas-math.ts +231 -33
  262. package/corpus/templates/design/shared/code-layer.ts +122 -8
  263. package/corpus/templates/design/shared/component-library.ts +161 -0
  264. package/corpus/templates/design/shared/db-conflict.ts +27 -0
  265. package/corpus/templates/design/shared/design-source-capabilities.ts +2 -1
  266. package/corpus/templates/design/shared/figma-url.ts +149 -0
  267. package/corpus/templates/design/shared/pen-path.ts +16 -7
  268. package/corpus/templates/design/shared/source-mode.ts +1 -0
  269. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +7 -19
  270. package/corpus/templates/plan/changelog/2026-07-09-canvas-panning-now-keeps-zoom-fixed-even-at-the-edges.md +6 -0
  271. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +16 -1
  272. package/corpus/templates/slides/app/context/DeckContext.tsx +311 -70
  273. package/corpus/templates/slides/changelog/2026-07-09-fixed-the-slide-rail-going-permanently-stale-during-a-long-a.md +6 -0
  274. package/dist/agent/production-agent.d.ts +82 -7
  275. package/dist/agent/production-agent.d.ts.map +1 -1
  276. package/dist/agent/production-agent.js +180 -17
  277. package/dist/agent/production-agent.js.map +1 -1
  278. package/dist/agent/run-manager.d.ts.map +1 -1
  279. package/dist/agent/run-manager.js +63 -14
  280. package/dist/agent/run-manager.js.map +1 -1
  281. package/dist/agent/run-store.d.ts +46 -0
  282. package/dist/agent/run-store.d.ts.map +1 -1
  283. package/dist/agent/run-store.js +129 -22
  284. package/dist/agent/run-store.js.map +1 -1
  285. package/dist/cli/design-connect.d.ts +6 -0
  286. package/dist/cli/design-connect.d.ts.map +1 -1
  287. package/dist/cli/design-connect.js +336 -139
  288. package/dist/cli/design-connect.js.map +1 -1
  289. package/dist/cli/skills.d.ts.map +1 -1
  290. package/dist/cli/skills.js +14 -0
  291. package/dist/cli/skills.js.map +1 -1
  292. package/dist/client/AssistantChat.d.ts +2 -0
  293. package/dist/client/AssistantChat.d.ts.map +1 -1
  294. package/dist/client/AssistantChat.js +19 -3
  295. package/dist/client/AssistantChat.js.map +1 -1
  296. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  297. package/dist/client/MultiTabAssistantChat.js +38 -18
  298. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  299. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  300. package/dist/client/agent-chat-adapter.js +48 -12
  301. package/dist/client/agent-chat-adapter.js.map +1 -1
  302. package/dist/client/agent-chat.d.ts +60 -0
  303. package/dist/client/agent-chat.d.ts.map +1 -1
  304. package/dist/client/agent-chat.js +122 -1
  305. package/dist/client/agent-chat.js.map +1 -1
  306. package/dist/client/chat/index.d.ts +1 -1
  307. package/dist/client/chat/index.d.ts.map +1 -1
  308. package/dist/client/chat/index.js +1 -1
  309. package/dist/client/chat/index.js.map +1 -1
  310. package/dist/client/extensions/EmbeddedExtension.d.ts +12 -0
  311. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  312. package/dist/client/extensions/EmbeddedExtension.js +36 -2
  313. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  314. package/dist/client/index.d.ts +1 -1
  315. package/dist/client/index.d.ts.map +1 -1
  316. package/dist/client/index.js +1 -1
  317. package/dist/client/index.js.map +1 -1
  318. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  319. package/dist/client/rich-markdown-editor/useCollabReconcile.js +18 -6
  320. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  321. package/dist/collab/awareness-store.d.ts +5 -0
  322. package/dist/collab/awareness-store.d.ts.map +1 -1
  323. package/dist/collab/awareness-store.js +19 -12
  324. package/dist/collab/awareness-store.js.map +1 -1
  325. package/dist/collab/awareness.d.ts +2 -2
  326. package/dist/collab/awareness.d.ts.map +1 -1
  327. package/dist/collab/client.d.ts.map +1 -1
  328. package/dist/collab/client.js +13 -5
  329. package/dist/collab/client.js.map +1 -1
  330. package/dist/collab/index.d.ts +1 -0
  331. package/dist/collab/index.d.ts.map +1 -1
  332. package/dist/collab/index.js +1 -0
  333. package/dist/collab/index.js.map +1 -1
  334. package/dist/collab/recent-edits.d.ts +3 -1
  335. package/dist/collab/recent-edits.d.ts.map +1 -1
  336. package/dist/collab/recent-edits.js +57 -2
  337. package/dist/collab/recent-edits.js.map +1 -1
  338. package/dist/collab/struct-routes.d.ts +1 -1
  339. package/dist/index.browser.d.ts +1 -1
  340. package/dist/index.browser.d.ts.map +1 -1
  341. package/dist/index.browser.js +1 -1
  342. package/dist/index.browser.js.map +1 -1
  343. package/dist/observability/routes.d.ts +5 -5
  344. package/dist/progress/routes.d.ts +1 -1
  345. package/dist/resources/handlers.d.ts +3 -3
  346. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  347. package/dist/server/agent-chat-plugin.js +87 -10
  348. package/dist/server/agent-chat-plugin.js.map +1 -1
  349. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  350. package/dist/server/builder-design-systems.d.ts +15 -0
  351. package/dist/server/builder-design-systems.d.ts.map +1 -1
  352. package/dist/server/builder-design-systems.js +9 -4
  353. package/dist/server/builder-design-systems.js.map +1 -1
  354. package/package.json +1 -1
@@ -248,6 +248,92 @@ design-system indexing just to insert a component. Use
248
248
  component/component set with provenance. Styles and variables still belong in
249
249
  the Builder-backed design-system path above.
250
250
 
251
+ ### Import from Figma (pixel-accurate frame import)
252
+
253
+ **When the user pastes a Figma frame/screen link and wants a real, editable
254
+ Design screen** (not a rendered image, not a component insert), use
255
+ `import-figma-frame` instead of `list-figma-library-assets` +
256
+ `insert-figma-library-asset`:
257
+
258
+ ```bash
259
+ pnpm action import-figma-frame --figmaUrl "https://www.figma.com/design/<fileKey>/<name>?node-id=<id>"
260
+ # or
261
+ pnpm action import-figma-frame --fileKey "<fileKey>" --nodeId "12:34" --designId "<designId>"
262
+ ```
263
+
264
+ - Accepts a full Figma URL (design/file/proto share links, including
265
+ `/branch/<key>/` branch URLs — the branch's own key is used automatically) or
266
+ an explicit `fileKey` + `nodeId`. If `nodeId` is omitted, the file's first
267
+ top-level frame is imported.
268
+ - Maps the node tree to real HTML/CSS: exact position/size, auto-layout as
269
+ flexbox, text (font, line-height, letter-spacing, case, decoration, align),
270
+ fills (solid/gradient/image, correctly layered and gradient-angle-derived,
271
+ not a default angle), strokes (including the CENTER/INSIDE/OUTSIDE
272
+ distinction), per-corner radii, shadows/blur, opacity, and blend modes.
273
+ Vector networks, boolean operations, and other structurally unsupported node
274
+ types are rendered as an exact PNG at 2x scale instead of an approximated
275
+ shape guess.
276
+ - Saves the result as a new screen via the same import path as other Design
277
+ imports (`saveImportedDesignFiles`), placed on the overview canvas.
278
+ - Returns a `fidelityReport` — `exactCount`, `approximated` (properties CSS
279
+ can only approximate: rotation, per-side stroke weights, radial/angular/
280
+ diamond gradients, blur radius scale), and `imageFallbacks` (subtrees
281
+ rendered as PNG instead of structural HTML). Read this back to the user when
282
+ a design has non-trivial fallbacks so they know what to expect if they later
283
+ edit that subtree.
284
+ - After import, treat the screen like any other: `view-screen`,
285
+ `get-design-snapshot`, `apply-visual-edit` / `edit-design` all work normally
286
+ on it.
287
+ - For a file's published FILL/TEXT/EFFECT/GRID styles (name, description, node
288
+ id — not full token values), use `get-figma-styles` with `fileUrl`/`fileKey`.
289
+ This is the file's Styles panel, not the Enterprise Variables API; full
290
+ design-token extraction still routes through the Builder-backed
291
+ `index-design-system-with-builder` path above.
292
+
293
+ #### Paste from Figma (Cmd+C/Cmd+V) vs. a copied frame link
294
+
295
+ A plain clipboard paste (Cmd+C in Figma, Cmd+V on the Design canvas) is
296
+ handled separately from `import-figma-frame`, because a clipboard paste and a
297
+ copied frame **link** carry fundamentally different information:
298
+
299
+ - **A copied frame link** (`?node-id=...`) names an exact node. Always exact —
300
+ use `import-figma-frame`.
301
+ - **A plain clipboard paste** only carries Figma's `figmeta` marker, which is
302
+ `{fileKey, pasteID, dataType}` — **no node id at all**, and `pasteID` is an
303
+ ephemeral, server-side identifier that the public REST API can't resolve
304
+ back to a node. So a clipboard paste can only ever get an exact node import
305
+ on a **best-effort match**, never a guarantee.
306
+
307
+ The canvas paste listener (`app/lib/figma-clipboard.ts` +
308
+ `import-figma-clipboard`) handles this automatically:
309
+
310
+ 1. Decodes `figmeta` from the pasted HTML client-side
311
+ (`extractFigmeta`/`resolveFigmaPasteImportCall`) to decide whether to call
312
+ `import-figma-clipboard` (figmeta present) or the legacy
313
+ `import-design-source` HTML path (no figmeta — not a Figma paste, or an
314
+ older Figma client that doesn't emit the marker).
315
+ 2. `import-figma-clipboard` fetches the file's shallow structure (top-level
316
+ frames + their direct children, `server/lib/figma-node-import.ts`'s
317
+ `fetchFileStructure(fileKey, 3)`) and heuristically matches it against the
318
+ pasted content's visible text (`server/lib/figma-clipboard-match.ts`):
319
+ a frame is only imported when its **name** or at least **two distinct
320
+ text-layer contents** appear verbatim in the paste. Anything ambiguous or
321
+ unmatched imports **nothing structural** — it never guesses and never
322
+ imports the whole file uninvited.
323
+ 3. On a confident match, the matched node(s) are fetched and mapped through
324
+ the same `buildScreenFilesFromFigmaNodes` core `import-figma-frame` uses
325
+ (`strategy: "restNodes"` in the result, with a `fidelityReport`).
326
+ 4. Otherwise it falls back to the legacy visible-HTML paste
327
+ (`strategy: "htmlFallback"`), and reports why via `matchStatus`
328
+ (`"ambiguous"`, `"none"`, or `"error"`) and `figmaApiKeyMissing` (no
329
+ `FIGMA_ACCESS_TOKEN` configured). The canvas paste toast surfaces a hint in
330
+ both cases: connect the Figma access token, or paste a frame **link**
331
+ instead for a guaranteed-exact import.
332
+
333
+ Tell users who want guaranteed pixel-exact imports to copy a frame **link**
334
+ ("Copy link to selection" in Figma), not just Cmd+C — a plain paste is
335
+ convenient but only best-effort.
336
+
251
337
  ### Source: Brand Analysis (combines website + notes)
252
338
 
253
339
  ```bash
@@ -17,6 +17,9 @@ How to export designs and generate handoff documentation for developers converti
17
17
  with the editor's Download SVG command. The editor's own Download SVG
18
18
  command captures the live browser DOM for the most faithful snapshot; use
19
19
  the action when you need agent-side SVG export without a live browser.
20
+ **This is not importable into Figma as editable vectors** — Figma cannot
21
+ parse `foreignObject` content, so it stays an opaque embedded HTML blob.
22
+ Use `export-design-as-figma-svg` (below) when the destination is Figma.
20
23
  - **PNG**: there is no PNG export action. Point the user to the editor's
21
24
  download menu (Download PNG) — PNG export is a client-side rasterization of
22
25
  the live canvas and is not exposed as an agent action.
@@ -88,6 +91,60 @@ pnpm action export-pdf --id <designId>
88
91
 
89
92
  Returns all design data and files needed for the client to render a PDF.
90
93
 
94
+ ## Export to Figma (SVG)
95
+
96
+ `export-design-as-figma-svg` exports a design screen (or a selected
97
+ element's subtree) as a genuinely VECTOR SVG document — real
98
+ `<rect>`/`<path>`/`<text>`/`<image>` markup with
99
+ `<linearGradient>`/`<radialGradient>`/`<filter>` defs. Figma's SVG importer
100
+ parses this into normal, editable layers (rect/path/gradients/filters stay
101
+ editable). This is a different artifact from `export-svg` above, whose
102
+ `foreignObject` wrapper Figma cannot import as vectors at all.
103
+
104
+ ```bash
105
+ pnpm action export-design-as-figma-svg --designId <designId>
106
+ ```
107
+
108
+ Optional args:
109
+
110
+ - `fileId` / `filename` — pick a specific screen (defaults to `index.html`).
111
+ - `nodeId` — scope the export to one selected element's subtree via its
112
+ `data-agent-native-node-id`, instead of the whole screen.
113
+ - `embedImages` (default `true`) — fetch and inline `http(s)` image sources
114
+ and background-images as `data:` URIs, so the SVG is self-contained for
115
+ clipboard paste. Set `false` to keep absolute URLs instead.
116
+
117
+ Returns `{ svg, filename, report, filePath? }`. `report` classifies every
118
+ element as `vectorized`, `approximated` (mapped with a documented caveat —
119
+ e.g. a non-square gradient angle, a non-uniform border, a radial gradient's
120
+ shape/position), `rasterized` (video/canvas/iframe content, and any element
121
+ with `backdrop-filter`, which SVG cannot express — embedded as a cropped
122
+ screenshot instead), or `omitted`. If no headless Chromium binary is
123
+ available in the current environment (expected in hosted/serverless
124
+ deploys), the action returns `{ ok: false, reason }` instead of throwing —
125
+ fall back to `export-svg` or `export-html`.
126
+
127
+ **Vectorized-text caveat**: Figma converts every imported SVG `<text>`
128
+ element to outlined vector paths on paste/drag-import. The exported
129
+ geometry is pixel-exact, but text pasted from this export is no longer
130
+ live, editable type in Figma — it's outlines, the same way any other
131
+ SVG-authoring tool's text becomes outlines on import. This is a Figma
132
+ import limitation, not a defect in the export; the report's
133
+ `vectorizedTextCaveat` field carries this note for the agent/user.
134
+
135
+ **Getting it into Figma**: two supported paths —
136
+
137
+ 1. **Copy, then paste into Figma.** In the editor, right-click a selected
138
+ element or the canvas and choose **Copy as SVG** (Copy/Paste as ▸ Copy as
139
+ SVG). This writes the SVG markup to the system clipboard as `text/plain`
140
+ (the MIME Figma's own paste handler reads for "paste as vector shapes")
141
+ plus `image/svg+xml` as a secondary representation. Paste directly into a
142
+ Figma canvas.
143
+ 2. **Download, then drag-import.** Figma's file browser also accepts a
144
+ plain `.svg` file dropped/imported directly — save the `svg` string
145
+ returned by the action to a `.svg` file and drag it into a Figma page the
146
+ same way you'd import any other SVG asset.
147
+
91
148
  ## Coding Handoff
92
149
 
93
150
  When a user wants to convert an Alpine.js + Tailwind prototype into production
@@ -208,6 +208,17 @@ the connected app's text/code files through the bridge
208
208
  - Saves are conflict-checked against the file's on-disk version — a file that
209
209
  changed since it was read fails with a version conflict instead of being
210
210
  overwritten.
211
+ - React/TSX canvas edits use build/debug provenance to locate the responsible
212
+ source, but structural meaning belongs to the coding agent. Do not apply a
213
+ generic AST reparent/group/ungroup transform. Hand off the exact subject and
214
+ target source anchors plus their runtime relationship; repeated `.map()`
215
+ instances, shared components, dynamic expressions, and cross-file edits
216
+ always require semantic inspection.
217
+ - For every semantic React write, read the file first, pass that exact
218
+ `versionHash` to `write-local-file` with `requireExpectedVersionHash: true`,
219
+ re-read and re-plan if it conflicts, and
220
+ verify the resulting HMR/runtime state before treating the preview as saved.
221
+ Human write consent remains mandatory and cannot be granted by an agent.
211
222
 
212
223
  ## Verification
213
224
 
@@ -67,6 +67,17 @@ patterns live in `.agents/skills/`.
67
67
  or pass it as an action parameter. Figma styles and variables are design-system
68
68
  inputs, not draggable media assets; route full file/design-system extraction
69
69
  through Builder-backed indexing.
70
+ - To import a Figma frame/screen as a real, editable Design screen (not a
71
+ rendered image), use `import-figma-frame` with a `figmaUrl` (or
72
+ `fileKey`/`nodeId`) — it maps position, auto-layout, text, fills/gradients,
73
+ strokes, corner radii, effects, opacity, and blend modes pixel-accurately,
74
+ falling back to an exact PNG only for vector networks/boolean ops/unsupported
75
+ node types, and saves the result as a new screen. Read the returned
76
+ `fidelityReport` (`approximated`, `imageFallbacks`) back to the user when
77
+ non-trivial. Use `get-figma-styles` for a file's published style names (not
78
+ the Enterprise Variables API; full token extraction still routes through
79
+ Builder-backed indexing). See the `design-systems` skill's "Import from
80
+ Figma" section.
70
81
  - Use Alpine.js and Tailwind CDN for interactive prototypes. Prefer Alpine
71
82
  directives over raw inline event handlers.
72
83
  - Navigate between prototype screens with Alpine state (`x-show`), a
@@ -112,9 +123,14 @@ patterns live in `.agents/skills/`.
112
123
  workflow.
113
124
  - Persist useful work early: create/update the design and files as soon as a
114
125
  coherent candidate exists, then iterate.
115
- - For non-trivial new design prompts, ask before generating: create/open the
116
- design shell, call `show-design-questions`, stop while the main canvas shows
117
- the questions, then continue from the user's answers.
126
+ - For a brief or ambiguous _new_ design prompt, ask before generating:
127
+ create/open the design shell, call `show-design-questions` with a small
128
+ tailored set, stop while the main canvas shows the questions, then continue
129
+ from the user's answers. Skip asking when the prompt is already specific,
130
+ it's a tweak/edit to an existing design, the user already answered a
131
+ question set for this design and is now iterating, or they say "decide for
132
+ me"/"surprise me"/"just build it" — see the `design-generation` skill for
133
+ how to size and word the questions.
118
134
  - For multiple screens/states, call `generate-screens` first. It opens the
119
135
  infinite screen overview and returns target filenames plus `canvasFrame`
120
136
  placements. Then call `generate-design` with those files and pass the matching
@@ -202,12 +218,27 @@ patterns live in `.agents/skills/`.
202
218
  layers: text, classes, styles, attributes, source order, and small structural
203
219
  changes. Use it for selected-element edits before falling back to full
204
220
  `update-design` / `generate-design` rewrites.
221
+ - For localhost React/TSX screens, treat compiler/debug metadata (project-relative
222
+ source file, line, column, component, and runtime multiplicity) as evidence
223
+ for locating source, not as permission to run a generic AST transform.
224
+ Compiler tooling may verify an anchor, classify a literal edit, and validate
225
+ syntax. Reparenting, grouping/ungrouping, wrappers, dynamic expressions,
226
+ repeated `.map()` instances, shared components, and cross-file changes must
227
+ be handed to the coding agent with both runtime relationships and exact
228
+ source anchors so it can inspect the surrounding program semantics.
229
+ - A localhost React handoff must read each source file first, write with the
230
+ exact returned `versionHash` as `expectedVersionHash` with
231
+ `requireExpectedVersionHash: true`, re-read and re-plan on conflict, and leave the
232
+ optimistic canvas preview in place until the dev server/HMR confirms the
233
+ runtime result. Never report a semantic canvas change as persisted merely
234
+ because it was submitted to the coding agent.
205
235
  - Prefer `data-agent-native-layer-name="Readable name"` on meaningful elements.
206
236
  The projection uses it before semantic/text fallbacks, and layer renames should
207
237
  persist by updating that attribute.
208
- - Current limitation: visual code-layer edits are HTML-only. Localhost source
209
- mode can list and resolve local routes now, but file reads/writes remain a
210
- bridge contract until explicit permission controls are hardened.
238
+ - Inline/Alpine screens continue to use deterministic HTML code-layer edits.
239
+ Localhost React/TSX screens use the semantic coding-agent handoff above;
240
+ deterministic direct React writes remain intentionally limited to narrowly
241
+ proven literal edits and never include generic structural transforms.
211
242
 
212
243
  ## Code Workspace
213
244
 
@@ -282,12 +313,16 @@ patterns live in `.agents/skills/`.
282
313
  and live app captures. See the same skill section.
283
314
  - **Components**: `create-component` promotes a selected element into a
284
315
  recognised reusable component; `index-components` scans a design's HTML for
285
- existing component annotations; `get-component-details`,
316
+ existing component annotations; `list-design-components` scans all HTML
317
+ screens for swap targets; `get-component-details`,
286
318
  `preview-component-prop-edit`, `apply-component-prop-edit`, and
287
319
  `open-component-source` inspect, preview, persist, and navigate to a
288
- component instance. See the `design-generation` skill's "Component reuse"
289
- section promote a 3+ times repeated pattern instead of inventing another
290
- near-duplicate.
320
+ component instance. Use `go-to-main-component` to select the earliest known
321
+ instance, `swap-component-instance` to replace an inline/Alpine instance
322
+ while preserving same-named prop overrides, and
323
+ `detach-component-instance` to turn an instance into plain editable markup.
324
+ See the `design-generation` skill's "Component reuse" section — promote a
325
+ 3+ times repeated pattern instead of inventing another near-duplicate.
291
326
 
292
327
  ## Full App Building
293
328
 
@@ -25,6 +25,7 @@ import {
25
25
  type CanvasFrameGeometry,
26
26
  type CanvasFramePlacement,
27
27
  } from "../shared/canvas-frames.js";
28
+ import { isUniqueConstraintViolation } from "../shared/db-conflict.js";
28
29
  import {
29
30
  makeLocalhostRouteId,
30
31
  titleFromRoutePath,
@@ -488,6 +489,18 @@ export default defineAction({
488
489
  height: number;
489
490
  }> = [];
490
491
  const placementIntents: PlacementIntent[] = [];
492
+ // Duplicate-route guard: `existingFiles`/`existingByFilename`/the
493
+ // route-candidate lookups below are all snapshotted ONCE above the loop
494
+ // and never refreshed as new files are inserted mid-loop, so two entries
495
+ // in the SAME `requestedRoutes` call that resolve to the same route
496
+ // (repeated `paths`, or a `routeId` and a `path` naming the same route)
497
+ // each independently see "no existing match" and each insert a fresh
498
+ // `design_files` row — two overlapping screens for one route. Track
499
+ // routeIds already processed THIS call (keyed with width/height so an
500
+ // intentional multi-viewport request for the same route still creates
501
+ // its distinct variants) and skip later duplicates outright.
502
+ const seenRouteRequestKeys = new Set<string>();
503
+ let placementIndex = 0;
491
504
 
492
505
  for (let index = 0; index < requestedRoutes.length; index += 1) {
493
506
  const input = requestedRoutes[index]!;
@@ -505,6 +518,9 @@ export default defineAction({
505
518
  );
506
519
  const routeId =
507
520
  input.routeId ?? manifestRoute?.id ?? makeLocalhostRouteId(path);
521
+ const routeRequestKey = `${routeId}::${input.width ?? ""}x${input.height ?? ""}`;
522
+ if (seenRouteRequestKeys.has(routeRequestKey)) continue;
523
+ seenRouteRequestKeys.add(routeRequestKey);
508
524
  const title =
509
525
  input.title ?? manifestRoute?.title ?? titleFromRoutePath(path);
510
526
  const sourceFile = input.sourceFile ?? manifestRoute?.sourceFile;
@@ -619,7 +635,9 @@ export default defineAction({
619
635
  const filename =
620
636
  existing?.filename ??
621
637
  uniqueFilename(path, usedFilenames, preferredFilename);
622
- const fileId = existing?.id ?? nanoid();
638
+ // Reassigned below if a concurrent request wins the insert race for
639
+ // this exact (designId, filename) pair — see the `else` branch.
640
+ let fileId = existing?.id ?? nanoid();
623
641
  const existingScreenMetadata = existing
624
642
  ? metadataForFile(
625
643
  existing.id,
@@ -656,16 +674,50 @@ export default defineAction({
656
674
  await seedFromText(existing.id, url);
657
675
  }
658
676
  } else {
659
- await db.insert(schema.designFiles).values({
660
- id: fileId,
661
- designId,
662
- filename,
663
- fileType: "html",
664
- content: url,
665
- createdAt: now,
666
- updatedAt: now,
667
- });
668
- await seedFromText(fileId, url);
677
+ try {
678
+ await db.insert(schema.designFiles).values({
679
+ id: fileId,
680
+ designId,
681
+ filename,
682
+ fileType: "html",
683
+ content: url,
684
+ createdAt: now,
685
+ updatedAt: now,
686
+ });
687
+ await seedFromText(fileId, url);
688
+ } catch (err) {
689
+ if (!isUniqueConstraintViolation(err)) throw err;
690
+ // Cross-request race: this snapshot's `existingFiles` query ran
691
+ // before a concurrent add-localhost-screens call (for the same
692
+ // route) committed its own insert, so both requests independently
693
+ // computed the same deterministic filename and both tried to
694
+ // create it. The `design_files_design_filename_unique_idx` unique
695
+ // index (see server/plugins/db.ts) turns the loser's insert into
696
+ // this error instead of a silent duplicate screen. Recover by
697
+ // adopting whichever row actually won the race and updating its
698
+ // content, the same way the `existing` branch above does.
699
+ const [winner] = await db
700
+ .select()
701
+ .from(schema.designFiles)
702
+ .where(
703
+ and(
704
+ eq(schema.designFiles.designId, designId),
705
+ eq(schema.designFiles.filename, filename),
706
+ ),
707
+ )
708
+ .limit(1);
709
+ if (!winner) throw err;
710
+ fileId = winner.id;
711
+ await db
712
+ .update(schema.designFiles)
713
+ .set({ content: url, fileType: "html", updatedAt: now })
714
+ .where(eq(schema.designFiles.id, winner.id));
715
+ if (await hasCollabState(winner.id)) {
716
+ await applyText(winner.id, url, "content", "agent");
717
+ } else {
718
+ await seedFromText(winner.id, url);
719
+ }
720
+ }
669
721
  }
670
722
 
671
723
  savedScreens.push({
@@ -688,12 +740,13 @@ export default defineAction({
688
740
  x:
689
741
  input.x ??
690
742
  existingFrame?.x ??
691
- layoutStartX + index * (width + layoutGap),
743
+ layoutStartX + placementIndex * (width + layoutGap),
692
744
  y: input.y ?? existingFrame?.y ?? layoutStartY,
693
745
  width,
694
746
  height,
695
- z: input.z ?? existingFrame?.z ?? index,
747
+ z: input.z ?? existingFrame?.z ?? placementIndex,
696
748
  };
749
+ placementIndex += 1;
697
750
  placementIntents.push({
698
751
  fileId,
699
752
  filename,
@@ -12,11 +12,12 @@
12
12
  * HTML attribute on the component root.
13
13
  * - `classReplace` — replaces one Tailwind class with another on the root node.
14
14
  *
15
- * **Tier B (real-app, localhost / fusion):** prop writes require the
16
- * `applyEdit` source capability. Localhost sources have it (design bridge +
17
- * user write consent); fusion sources gain it after bridge hardening. For
18
- * sources without it the action returns a `ctaRequired: true` response and
19
- * does not modify any source.
15
+ * **Real-app sources (localhost / fusion):** deliberately fail closed. This
16
+ * action's patcher operates on SQL-backed HTML design files; it must never be
17
+ * reused for compiled JSX/TSX source. Real-app prop persistence needs a
18
+ * dedicated consented, version-guarded bridge transform. Until that exists,
19
+ * callers may preview but this action returns `ctaRequired: true` without
20
+ * reading or modifying a design file.
20
21
  *
21
22
  * See DESIGN-STUDIO-PLAN.md §6.1, §7 (preview/apply contract), §11 phase 2.
22
23
  */
@@ -38,10 +39,11 @@ import { z } from "zod";
38
39
  import { getDb, schema } from "../server/db/index.js";
39
40
  import "../server/db/index.js"; // ensure registerShareableResource runs
40
41
  import {
42
+ prepareInlineSourceEdit,
43
+ SourceWorkspaceEditConflictError,
41
44
  writeInlineSourceFile,
42
45
  type SourceWorkspaceFile,
43
46
  } from "../server/source-workspace.js";
44
- import { resolveSourceCapabilities } from "../shared/capability-resolver.js";
45
47
  import {
46
48
  applyVisualEdit,
47
49
  buildCodeLayerProjection,
@@ -56,9 +58,7 @@ import {
56
58
  componentNameFor,
57
59
  componentNodeIdMatches,
58
60
  } from "../shared/component-model.js";
59
- import { hasCapability } from "../shared/design-source-capabilities.js";
60
61
  import { designSourceTypeFromData } from "../shared/source-mode.js";
61
- import { sourceContentHash } from "../shared/source-workspace.js";
62
62
 
63
63
  // ─── Helpers ──────────────────────────────────────────────────────────────────
64
64
 
@@ -174,8 +174,8 @@ export default defineAction({
174
174
  "For inline/Alpine designs, edits the data-agent-native-prop-* attributes, " +
175
175
  "x-data expression, or class list of the component root via the deterministic " +
176
176
  "HTML-patch path (same seam as apply-visual-edit). " +
177
- "For real-app sources, the applyEdit capability must be available; if not, " +
178
- "returns ctaRequired=true without modifying any file.",
177
+ "For real-app sources, returns ctaRequired=true without modifying any file; " +
178
+ "compiled source requires a dedicated consented bridge transform.",
179
179
  schema: z.object({
180
180
  designId: z.string().describe("Design project ID"),
181
181
  nodeId: z
@@ -234,19 +234,21 @@ export default defineAction({
234
234
  .optional(),
235
235
  }),
236
236
  run: async ({ designId, nodeId, fileId, edit, source }) => {
237
- const db = getDb();
238
-
239
237
  // ── Access check ────────────────────────────────────────────────────────
240
238
  const access = await resolveAccess("design", designId);
241
239
  if (!access) throw new Error("Design not found");
242
240
 
243
- // ── Source type + capability gate ────────────────────────────────────────
241
+ // ── Source type gate ────────────────────────────────────────────────────
244
242
  const rawData = (access.resource as { data?: unknown }).data;
245
243
  const sourceType = designSourceTypeFromData(rawData);
246
- const caps = resolveSourceCapabilities(sourceType);
247
244
 
248
- // Real-app sources gate on `applyEdit` (bridge write hardening).
249
- if (sourceType !== "inline" && !hasCapability(caps, "applyEdit")) {
245
+ // Fail closed for every real-app tier even if its generic capability map
246
+ // advertises applyEdit. This action only knows how to patch SQL-backed HTML;
247
+ // allowing localhost through here could report success for the mirror while
248
+ // leaving the real JSX/TSX file untouched. A future compiled-source action
249
+ // must perform consent, canonical path resolution, AST anchoring, and an
250
+ // expected-version bridge write as one dedicated transaction.
251
+ if (sourceType !== "inline") {
250
252
  return {
251
253
  designId,
252
254
  nodeId,
@@ -254,13 +256,14 @@ export default defineAction({
254
256
  persisted: false,
255
257
  ctaRequired: true,
256
258
  ctaMessage:
257
- "Prop write-back to real app sources requires the bridge applyEdit " +
258
- "capability, which lands with bridge write hardening. " +
259
+ "Prop write-back to real app sources requires a dedicated consented, " +
260
+ "version-guarded compiled-source transform. " +
259
261
  "Use preview-component-prop-edit to preview without persisting.",
260
262
  };
261
263
  }
262
264
 
263
265
  await assertAccess("design", designId, "editor");
266
+ const db = getDb();
264
267
 
265
268
  // ── Fetch file ───────────────────────────────────────────────────────────
266
269
  const conditions = [
@@ -289,12 +292,24 @@ export default defineAction({
289
292
 
290
293
  if (!file) throw new Error("Design HTML file not found.");
291
294
 
292
- if (
293
- source?.currentContent &&
294
- source.revision &&
295
- file.updatedAt &&
296
- source.revision !== file.updatedAt
297
- ) {
295
+ const workspaceFile: SourceWorkspaceFile = {
296
+ id: file.id,
297
+ designId: file.designId,
298
+ filename: file.filename,
299
+ fileType: "html",
300
+ content: file.content,
301
+ createdAt: null,
302
+ updatedAt: file.updatedAt,
303
+ };
304
+ let prepared: Awaited<ReturnType<typeof prepareInlineSourceEdit>>;
305
+ try {
306
+ prepared = await prepareInlineSourceEdit({
307
+ file: workspaceFile,
308
+ currentContent: source?.currentContent,
309
+ revision: source?.revision,
310
+ });
311
+ } catch (error) {
312
+ if (!(error instanceof SourceWorkspaceEditConflictError)) throw error;
298
313
  return {
299
314
  designId,
300
315
  nodeId,
@@ -307,18 +322,12 @@ export default defineAction({
307
322
  };
308
323
  }
309
324
 
310
- // Prefer explicit editor content after the caller's revision check, and use
311
- // the saved SQL content as the fallback. Collab/Yjs reads can be stale
312
- // across local dev worker processes and make prop controls lag behind.
313
- const html =
314
- typeof source?.currentContent === "string"
315
- ? source.currentContent
316
- : (file.content ?? "");
317
- // Capture the hash of this EXACT base — the same string the transform
318
- // below reads from — so persistEdit can pass it through as
319
- // expectedVersionHash. A re-read at persist time would hash whatever is
320
- // live "then" instead of proving this base is still current.
321
- const baseVersionHash = sourceContentHash(html);
325
+ // The transform runs against the caller's working copy (when supplied),
326
+ // while the persist CAS uses the live hash that working copy is allowed to
327
+ // replace. Keeping those identities separate preserves rapid unsaved prop
328
+ // edits without weakening concurrent-writer rejection.
329
+ const html = prepared.content;
330
+ const baseVersionHash = prepared.expectedVersionHash;
322
331
 
323
332
  // ── Resolve node ─────────────────────────────────────────────────────────
324
333
  const codeLayerSource: CodeLayerSource = {
@@ -32,7 +32,7 @@ import { z } from "zod";
32
32
  import { getDb, schema } from "../server/db/index.js";
33
33
  import "../server/db/index.js"; // ensure registerShareableResource runs
34
34
  import {
35
- readLiveSourceFile,
35
+ prepareInlineSourceEdit,
36
36
  writeInlineSourceFile,
37
37
  type SourceWorkspaceFile,
38
38
  } from "../server/source-workspace.js";
@@ -51,7 +51,6 @@ import {
51
51
  readTimelinePlaybackMode,
52
52
  withTimelinePlaybackMode,
53
53
  } from "../shared/motion-timeline.js";
54
- import { sourceContentHash } from "../shared/source-workspace.js";
55
54
 
56
55
  // ─── DoS-guard caps ──────────────────────────────────────────────────────────
57
56
  //
@@ -343,7 +342,15 @@ export default defineAction({
343
342
  .describe(
344
343
  "Current open editor HTML for the target file. When supplied, the " +
345
344
  "managed motion CSS is patched into this content instead of the " +
346
- "last SQL snapshot so in-flight local edits are preserved.",
345
+ "last SQL snapshot so in-flight local edits are preserved. " +
346
+ "revision must also be supplied.",
347
+ ),
348
+ revision: z
349
+ .string()
350
+ .optional()
351
+ .describe(
352
+ "design_files.updatedAt value the currentContent is based on. " +
353
+ "Required when currentContent is supplied.",
347
354
  ),
348
355
  }),
349
356
  run: async ({
@@ -357,6 +364,7 @@ export default defineAction({
357
364
  defaultEase,
358
365
  includeContent,
359
366
  currentContent: currentContentInput,
367
+ revision,
360
368
  }) => {
361
369
  const access = await assertAccess("design", designId, "editor");
362
370
 
@@ -378,6 +386,7 @@ export default defineAction({
378
386
  filename: schema.designFiles.filename,
379
387
  fileType: schema.designFiles.fileType,
380
388
  content: schema.designFiles.content,
389
+ updatedAt: schema.designFiles.updatedAt,
381
390
  })
382
391
  .from(schema.designFiles)
383
392
  .innerJoin(
@@ -398,40 +407,25 @@ export default defineAction({
398
407
  const fileId = file.id;
399
408
  const resolvedSourceRef = sourceRef ?? fileId;
400
409
 
401
- // The EDITABLE base for compiling/injecting CSS: prefer the caller's
402
- // in-flight editor snapshot when supplied (currentContentInput), else the
403
- // LIVE collab-authoritative content for this file (not the raw SQL row
404
- // a live editor's Y.Text may be ahead of the last SQL snapshot). Either
405
- // way, `baseVersionHash` is computed from the EXACT string chosen as the
406
- // base, at this same point in time, so it faithfully represents "what
407
- // this transform's base was" for the persist's optimistic-concurrency
408
- // check below — never a fresh re-hash of already-transformed content
409
- // (that would trivially match itself and prove nothing raced).
410
- let currentContent: string;
411
- let baseVersionHash: string;
412
- if (currentContentInput !== undefined) {
413
- // Caller supplied their own editor snapshot as the patch base. It may
414
- // already be stale relative to the live doc — that's fine.
415
- // writeInlineSourceFile's own guard (via readLiveSourceFile at write
416
- // time) correctly rejects the write if this base no longer matches
417
- // live state, exactly as designed for every other caller-supplied-base
418
- // action (see apply-component-prop-edit.ts / apply-shader-fill.ts).
419
- currentContent = currentContentInput;
420
- baseVersionHash = sourceContentHash(currentContent);
421
- } else {
422
- const workspaceFileForRead: SourceWorkspaceFile = {
410
+ // Keep the transform working copy separate from the live version it may
411
+ // replace. A caller snapshot can legitimately contain unsaved local edits
412
+ // beyond the matching SQL revision; the write CAS must guard the observed
413
+ // live base, not incorrectly hash those unsaved edits as if already live.
414
+ const prepared = await prepareInlineSourceEdit({
415
+ file: {
423
416
  id: file.id,
424
417
  designId: file.designId,
425
418
  filename: file.filename,
426
419
  fileType: file.fileType,
427
420
  content: file.content,
428
421
  createdAt: null,
429
- updatedAt: null,
430
- };
431
- const live = await readLiveSourceFile(workspaceFileForRead);
432
- currentContent = live.content;
433
- baseVersionHash = live.versionHash;
434
- }
422
+ updatedAt: file.updatedAt,
423
+ },
424
+ currentContent: currentContentInput,
425
+ revision,
426
+ });
427
+ const currentContent = prepared.content;
428
+ const baseVersionHash = prepared.expectedVersionHash;
435
429
 
436
430
  // ── 2. Compile tracks → CSS ─────────────────────────────────────────────
437
431
  const inputTracks = tracks as MotionTrack[];