@agent-native/core 0.92.9 → 0.92.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +29 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +181 -15
  5. package/corpus/core/src/agent/run-manager.ts +31 -4
  6. package/corpus/core/src/agent/run-store.ts +80 -0
  7. package/corpus/core/src/cli/design-connect.ts +433 -151
  8. package/corpus/core/src/cli/skills.ts +14 -0
  9. package/corpus/core/src/client/AssistantChat.tsx +25 -0
  10. package/corpus/core/src/client/MultiTabAssistantChat.tsx +40 -6
  11. package/corpus/core/src/client/agent-chat.ts +165 -1
  12. package/corpus/core/src/client/chat/index.ts +9 -1
  13. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +38 -2
  14. package/corpus/core/src/client/index.ts +5 -0
  15. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +18 -7
  16. package/corpus/core/src/collab/awareness-store.ts +23 -12
  17. package/corpus/core/src/collab/client.ts +16 -5
  18. package/corpus/core/src/collab/index.ts +4 -0
  19. package/corpus/core/src/collab/recent-edits.ts +65 -2
  20. package/corpus/core/src/deploy/build.ts +133 -5
  21. package/corpus/core/src/index.browser.ts +1 -0
  22. package/corpus/core/src/server/agent-chat-plugin.ts +98 -11
  23. package/corpus/core/src/server/builder-design-systems.ts +30 -4
  24. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +1 -1
  25. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +12 -6
  26. package/corpus/templates/clips/AGENTS.md +4 -0
  27. package/corpus/templates/clips/actions/create-recording.ts +2 -0
  28. package/corpus/templates/clips/actions/lib/create-recording-schema.ts +5 -3
  29. package/corpus/templates/clips/actions/request-transcript.ts +102 -33
  30. package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -0
  31. package/corpus/templates/clips/app/components/player/share-dialog.tsx +59 -19
  32. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +48 -4
  33. package/corpus/templates/clips/app/components/player/video-player.tsx +17 -12
  34. package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
  35. package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
  36. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  37. package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
  38. package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
  39. package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
  40. package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
  41. package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
  42. package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
  43. package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
  44. package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
  45. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -37
  46. package/corpus/templates/clips/app/routes/record.tsx +17 -4
  47. package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
  48. package/corpus/templates/clips/changelog/2026-07-09-desktop-popover-sits-closer-to-the-menu-bar.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-09-desktop-recording-optimization-progress-stays-visible-until-the-clip-opens.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-07-09-fixed-large-video-file-uploads-failing-before-their-resumabl.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-07-09-fixed-new-video-uploads-defaulting-to-private.md +6 -0
  52. package/corpus/templates/clips/changelog/2026-07-09-long-recordings-now-get-enough-time-to-download-their-media-.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-07-09-meeting-notes-no-longer-lowers-your-microphone-volume-for-ot.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-09-on-mobile-tapping-a-playing-clip-reveals-playback-controls-w.md +6 -0
  55. package/corpus/templates/clips/changelog/2026-07-09-regenerate-any-clip-transcript-from-its-saved-recording-or-r.md +6 -0
  56. package/corpus/templates/clips/changelog/2026-07-09-slack-previews-now-show-the-clip-length-alongside-its-descri.md +6 -0
  57. package/corpus/templates/clips/desktop/src/app.tsx +12 -2
  58. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +8 -1
  59. package/corpus/templates/clips/desktop/src/lib/recorder.ts +8 -5
  60. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +5 -5
  61. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +3 -0
  62. package/corpus/templates/clips/desktop/src/styles.css +6 -3
  63. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +29 -11
  64. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
  65. package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +103 -21
  66. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +117 -39
  67. package/corpus/templates/clips/server/lib/slack-unfurls.ts +20 -1
  68. package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +10 -4
  69. package/corpus/templates/clips/shared/recording-core.ts +12 -0
  70. package/corpus/templates/content/AGENTS.md +7 -6
  71. package/corpus/templates/content/actions/_document-flush.ts +132 -17
  72. package/corpus/templates/content/actions/_notion-action-utils.ts +15 -0
  73. package/corpus/templates/content/actions/create-and-link-notion-page.ts +2 -0
  74. package/corpus/templates/content/actions/link-notion-page.ts +2 -0
  75. package/corpus/templates/content/actions/pull-document.ts +5 -7
  76. package/corpus/templates/content/actions/pull-notion-page.ts +2 -0
  77. package/corpus/templates/content/actions/push-notion-page.ts +10 -0
  78. package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +2 -0
  79. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +88 -23
  80. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +32 -2
  81. package/corpus/templates/content/app/hooks/use-notion.ts +6 -6
  82. package/corpus/templates/content/app/i18n/zh-TW.ts +1 -0
  83. package/corpus/templates/content/app/i18n-data.ts +18 -0
  84. package/corpus/templates/content/changelog/2026-07-09-notion-conflict-choices-and-version-restores-preserve-docume.md +6 -0
  85. package/corpus/templates/content/server/lib/notion-sync.ts +80 -64
  86. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +29 -2
  87. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +86 -0
  88. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +57 -0
  89. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +11 -0
  90. package/corpus/templates/design/AGENTS.md +45 -10
  91. package/corpus/templates/design/actions/add-localhost-screens.ts +66 -13
  92. package/corpus/templates/design/actions/apply-component-prop-edit.ts +45 -36
  93. package/corpus/templates/design/actions/apply-motion-edit.ts +25 -31
  94. package/corpus/templates/design/actions/apply-shader-fill.ts +29 -42
  95. package/corpus/templates/design/actions/detach-component-instance.ts +337 -0
  96. package/corpus/templates/design/actions/export-design-as-figma-svg.ts +219 -0
  97. package/corpus/templates/design/actions/generate-design.ts +79 -12
  98. package/corpus/templates/design/actions/generate-screens.ts +57 -12
  99. package/corpus/templates/design/actions/get-figma-styles.ts +115 -0
  100. package/corpus/templates/design/actions/go-to-main-component.ts +254 -0
  101. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +4 -3
  102. package/corpus/templates/design/actions/import-design-source.ts +4 -31
  103. package/corpus/templates/design/actions/import-figma-clipboard.ts +134 -0
  104. package/corpus/templates/design/actions/import-figma-frame.ts +95 -0
  105. package/corpus/templates/design/actions/index-design-system-with-builder.ts +16 -1
  106. package/corpus/templates/design/actions/list-design-components.ts +104 -0
  107. package/corpus/templates/design/actions/list-figma-library-assets.ts +1 -24
  108. package/corpus/templates/design/actions/open-component-source.ts +2 -4
  109. package/corpus/templates/design/actions/present-design-variants.ts +412 -16
  110. package/corpus/templates/design/actions/preview-source-edit.ts +14 -1
  111. package/corpus/templates/design/actions/run-design-audit.ts +25 -3
  112. package/corpus/templates/design/actions/show-design-questions.ts +10 -0
  113. package/corpus/templates/design/actions/swap-component-instance.ts +544 -0
  114. package/corpus/templates/design/actions/take-design-screenshot.ts +45 -60
  115. package/corpus/templates/design/actions/write-local-file.ts +25 -1
  116. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +152 -0
  117. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1077 -58
  118. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +5 -5
  119. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +73 -21
  120. package/corpus/templates/design/app/components/design/EditPanel.tsx +16 -11
  121. package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +364 -0
  122. package/corpus/templates/design/app/components/design/LayersPanel.tsx +180 -53
  123. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +0 -2
  124. package/corpus/templates/design/app/components/design/MotionDock.tsx +144 -21
  125. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1152 -167
  126. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +27 -5
  127. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +23 -2
  128. package/corpus/templates/design/app/components/design/TokensPanel.tsx +63 -49
  129. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1293 -54
  130. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +7 -0
  131. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +6 -1
  132. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +23 -6
  133. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +7 -0
  134. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +5 -0
  135. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +14 -2
  136. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +37 -2
  137. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open-guard.ts +25 -0
  138. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +19 -1
  139. package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +43 -5
  140. package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +27 -2
  141. package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +10 -1
  142. package/corpus/templates/design/app/components/design/code-workbench/store.tsx +19 -2
  143. package/corpus/templates/design/app/components/design/design-canvas/annotation-snapshot.ts +265 -0
  144. package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +16 -5
  145. package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +16 -2
  146. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +7 -2
  147. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +3 -1
  148. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +78 -8
  149. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +451 -73
  150. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +12 -4
  151. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +398 -272
  152. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +41 -7
  153. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +16 -3
  154. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +24 -4
  155. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +222 -4
  156. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +171 -78
  157. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +45 -5
  158. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +299 -28
  159. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +120 -150
  160. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +53 -1
  161. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +284 -117
  162. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +81 -0
  163. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +19 -14
  164. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +7 -0
  165. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +155 -0
  166. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +268 -46
  167. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +350 -31
  168. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +13 -4
  169. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +233 -23
  170. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +72 -2
  171. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +51 -11
  172. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +84 -11
  173. package/corpus/templates/design/app/components/design/inspector/index.ts +3 -0
  174. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +552 -0
  175. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +157 -0
  176. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +53 -0
  177. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +184 -0
  178. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +403 -26
  179. package/corpus/templates/design/app/components/design/multi-screen/types.ts +20 -1
  180. package/corpus/templates/design/app/components/design/types.ts +33 -0
  181. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +59 -7
  182. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +119 -17
  183. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +275 -63
  184. package/corpus/templates/design/app/hooks/use-question-flow.ts +14 -3
  185. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +67 -10
  186. package/corpus/templates/design/app/i18n/zh-TW.ts +91 -1
  187. package/corpus/templates/design/app/i18n-data.ts +1217 -10
  188. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +577 -0
  189. package/corpus/templates/design/app/lib/agent-chat.ts +23 -0
  190. package/corpus/templates/design/app/lib/design-import.ts +24 -0
  191. package/corpus/templates/design/app/lib/figma-clipboard.ts +157 -0
  192. package/corpus/templates/design/app/lib/figma-svg-copy.ts +221 -0
  193. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +143 -30
  194. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +15 -0
  195. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +33 -0
  196. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +38 -0
  197. package/corpus/templates/design/app/pages/design-editor/overview-annotation-context.ts +157 -0
  198. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +62 -6
  199. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +245 -0
  200. package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +586 -0
  201. package/corpus/templates/design/app/pages/design-editor/screen-auto-layout.ts +169 -0
  202. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +201 -1
  203. package/corpus/templates/design/changelog/2026-07-09-added-underline-strikethrough-and-text-case-uppercase-lowerc.md +6 -0
  204. package/corpus/templates/design/changelog/2026-07-09-annotate-now-draws-across-the-all-screens-canvas-without-swi.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-09-auto-layout-children-can-now-be-dragged-into-freeform-screen.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-09-board-shapes-now-stay-visible-and-nest-correctly-across-the-.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-09-component-source-navigation-now-opens-reliably-from-the-insp.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-09-cross-screen-local-react-layer-moves-now-hand-off-exact-sour.md +6 -0
  209. package/corpus/templates/design/changelog/2026-07-09-cross-screen-moves-undo-and-redo-now-keep-both-screens-stabl.md +6 -0
  210. package/corpus/templates/design/changelog/2026-07-09-detach-a-component-instance-into-plain-editable-eleme.md +6 -0
  211. package/corpus/templates/design/changelog/2026-07-09-dragging-layers-into-auto-layout-now-respects-ignore-auto-la.md +6 -0
  212. package/corpus/templates/design/changelog/2026-07-09-drawing-annotations-sent-to-the-agent-now-include-a-rendered.md +6 -0
  213. package/corpus/templates/design/changelog/2026-07-09-editor-sidebars-now-use-figma-matched-default-widths-for-a-r.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-09-empty-frames-and-rectangles-now-expose-full-auto-layout-cont.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-09-export-any-design-screen-as-a-real-vector-svg-for-figma-with.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-09-grid-auto-layout-now-uses-true-responsive-rows-and-columns-w.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-09-hidden-and-locked-screens-now-behave-consistently-between-la.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-09-image-fills-now-preserve-valid-urls-and-layered-fit-settings.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-09-import-figma-frames-as-pixel-accurate-design-screens-via-a-s.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-09-inspect-code-now-hides-internal-styling-metadata-and-wraps-l.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-09-inspector-blur-and-shadow-effects-now-preserve-other-effects.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-09-jump-to-a-components-other-instances-from-the-inspecto.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-09-keyboard-shortcuts-are-now-discoverable-in-a-figma-style-bot.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-09-layer-selection-now-keeps-cmd-or-ctrl-toggles-and-shift-rang.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-09-layers-dropped-into-auto-layout-now-participate-correctly-in.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-09-local-live-edit-screens-now-enter-and-leave-full-view-withou.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-09-local-preview-urls-now-live-in-the-inspector-instead-of-taki.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-can-now-be-hidden-and-locked-from-layers-.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-now-keep-exact-source-provenance-and-supp.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-09-local-react-visual-editing-now-loads-reliably-and-shows-live.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-09-paste-from-figma-cmd-v-now-imports-exact-editable-nodes-when.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-09-pressing-escape-after-selecting-a-screen-now-reveals-page-pr.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-09-right-click-overlapping-or-nested-objects-to-choose-the-exac.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-09-scale-edge-stretch-and-center-constraints-now-preserve-layer.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-09-screen-rows-can-now-be-reordered-in-layers-with-matching-can.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-09-screen-selection-stays-responsive-in-designs-with-hundreds-o.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-09-shift-a-now-enables-auto-layout-on-a-selected-screen-with-fl.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-09-swap-a-component-instance-for-another-from-the-libra.md +6 -0
  239. package/corpus/templates/design/changelog/2026-07-10-drawing-annotations-now-stay-available-to-retry-when-the.md +6 -0
  240. package/corpus/templates/design/e2e/global-setup.ts +3 -6
  241. package/corpus/templates/design/e2e/helpers.ts +27 -6
  242. package/corpus/templates/design/server/lib/design-to-figma-svg.ts +1412 -0
  243. package/corpus/templates/design/server/lib/figma-clipboard-match.ts +196 -0
  244. package/corpus/templates/design/server/lib/figma-node-import.ts +311 -0
  245. package/corpus/templates/design/server/lib/figma-node-to-html.ts +1334 -0
  246. package/corpus/templates/design/server/lib/figma-paste-fallback.ts +64 -0
  247. package/corpus/templates/design/server/lib/playwright-runtime.ts +78 -0
  248. package/corpus/templates/design/server/plugins/db.ts +41 -0
  249. package/corpus/templates/design/server/source-workspace.ts +64 -0
  250. package/corpus/templates/design/shared/board-file.ts +13 -2
  251. package/corpus/templates/design/shared/canvas-math.ts +231 -33
  252. package/corpus/templates/design/shared/code-layer.ts +122 -8
  253. package/corpus/templates/design/shared/component-library.ts +161 -0
  254. package/corpus/templates/design/shared/db-conflict.ts +27 -0
  255. package/corpus/templates/design/shared/design-source-capabilities.ts +2 -1
  256. package/corpus/templates/design/shared/figma-url.ts +149 -0
  257. package/corpus/templates/design/shared/pen-path.ts +16 -7
  258. package/corpus/templates/design/shared/source-mode.ts +1 -0
  259. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +7 -19
  260. package/corpus/templates/plan/changelog/2026-07-09-canvas-panning-now-keeps-zoom-fixed-even-at-the-edges.md +6 -0
  261. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +16 -1
  262. package/corpus/templates/slides/app/context/DeckContext.tsx +311 -70
  263. package/corpus/templates/slides/changelog/2026-07-09-fixed-the-slide-rail-going-permanently-stale-during-a-long-a.md +6 -0
  264. package/dist/agent/production-agent.d.ts +79 -6
  265. package/dist/agent/production-agent.d.ts.map +1 -1
  266. package/dist/agent/production-agent.js +161 -14
  267. package/dist/agent/production-agent.js.map +1 -1
  268. package/dist/agent/run-manager.d.ts.map +1 -1
  269. package/dist/agent/run-manager.js +29 -5
  270. package/dist/agent/run-manager.js.map +1 -1
  271. package/dist/agent/run-store.d.ts +46 -0
  272. package/dist/agent/run-store.d.ts.map +1 -1
  273. package/dist/agent/run-store.js +61 -0
  274. package/dist/agent/run-store.js.map +1 -1
  275. package/dist/cli/design-connect.d.ts +6 -0
  276. package/dist/cli/design-connect.d.ts.map +1 -1
  277. package/dist/cli/design-connect.js +336 -139
  278. package/dist/cli/design-connect.js.map +1 -1
  279. package/dist/cli/skills.d.ts.map +1 -1
  280. package/dist/cli/skills.js +14 -0
  281. package/dist/cli/skills.js.map +1 -1
  282. package/dist/client/AssistantChat.d.ts +2 -0
  283. package/dist/client/AssistantChat.d.ts.map +1 -1
  284. package/dist/client/AssistantChat.js +19 -3
  285. package/dist/client/AssistantChat.js.map +1 -1
  286. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  287. package/dist/client/MultiTabAssistantChat.js +38 -18
  288. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  289. package/dist/client/agent-chat.d.ts +60 -0
  290. package/dist/client/agent-chat.d.ts.map +1 -1
  291. package/dist/client/agent-chat.js +122 -1
  292. package/dist/client/agent-chat.js.map +1 -1
  293. package/dist/client/chat/index.d.ts +1 -1
  294. package/dist/client/chat/index.d.ts.map +1 -1
  295. package/dist/client/chat/index.js +1 -1
  296. package/dist/client/chat/index.js.map +1 -1
  297. package/dist/client/extensions/EmbeddedExtension.d.ts +12 -0
  298. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  299. package/dist/client/extensions/EmbeddedExtension.js +36 -2
  300. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  301. package/dist/client/index.d.ts +1 -1
  302. package/dist/client/index.d.ts.map +1 -1
  303. package/dist/client/index.js +1 -1
  304. package/dist/client/index.js.map +1 -1
  305. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  306. package/dist/client/rich-markdown-editor/useCollabReconcile.js +18 -6
  307. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  308. package/dist/collab/awareness-store.d.ts +5 -0
  309. package/dist/collab/awareness-store.d.ts.map +1 -1
  310. package/dist/collab/awareness-store.js +19 -12
  311. package/dist/collab/awareness-store.js.map +1 -1
  312. package/dist/collab/awareness.d.ts +2 -2
  313. package/dist/collab/awareness.d.ts.map +1 -1
  314. package/dist/collab/client.d.ts.map +1 -1
  315. package/dist/collab/client.js +13 -5
  316. package/dist/collab/client.js.map +1 -1
  317. package/dist/collab/index.d.ts +1 -0
  318. package/dist/collab/index.d.ts.map +1 -1
  319. package/dist/collab/index.js +1 -0
  320. package/dist/collab/index.js.map +1 -1
  321. package/dist/collab/recent-edits.d.ts +3 -1
  322. package/dist/collab/recent-edits.d.ts.map +1 -1
  323. package/dist/collab/recent-edits.js +57 -2
  324. package/dist/collab/recent-edits.js.map +1 -1
  325. package/dist/collab/routes.d.ts +1 -1
  326. package/dist/deploy/build.d.ts +24 -0
  327. package/dist/deploy/build.d.ts.map +1 -1
  328. package/dist/deploy/build.js +102 -5
  329. package/dist/deploy/build.js.map +1 -1
  330. package/dist/index.browser.d.ts +1 -1
  331. package/dist/index.browser.d.ts.map +1 -1
  332. package/dist/index.browser.js +1 -1
  333. package/dist/index.browser.js.map +1 -1
  334. package/dist/notifications/routes.d.ts +1 -1
  335. package/dist/observability/routes.d.ts +5 -5
  336. package/dist/progress/routes.d.ts +1 -1
  337. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  338. package/dist/server/agent-chat-plugin.js +87 -10
  339. package/dist/server/agent-chat-plugin.js.map +1 -1
  340. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  341. package/dist/server/builder-design-systems.d.ts +15 -0
  342. package/dist/server/builder-design-systems.d.ts.map +1 -1
  343. package/dist/server/builder-design-systems.js +9 -4
  344. package/dist/server/builder-design-systems.js.map +1 -1
  345. package/package.json +1 -1
@@ -557,11 +557,11 @@ export async function prepareDesignConnectManifest(options = {}) {
557
557
  status: "available",
558
558
  reason: operation === "resolveNodeToFile"
559
559
  ? // resolveNodeToFile maps a runtime DOM node id (from the editor's
560
- // 'select' payload) to { file, line, component } provenance. The
561
- // bridge endpoint exists; per-element provenance data must be
562
- // emitted by the connected app at build time — see the provenance
563
- // note in the help text below.
564
- "Requires the connected app to emit data-source-file / data-source-line / data-component-name attributes (e.g. via @vitejs/plugin-react jsxDEV or a Babel source plugin)."
560
+ // 'select' payload) to { file, line, component } provenance.
561
+ // React development builds expose jsxDEV call sites through the
562
+ // Fiber debug stack; other runtimes/builds can emit explicit DOM
563
+ // provenance attributes — see the help text below.
564
+ "React development builds resolve jsxDEV call sites automatically; other runtimes can emit data-source-file / data-source-line / data-component-name attributes."
565
565
  : undefined,
566
566
  })),
567
567
  };
@@ -626,9 +626,10 @@ function sendText(res, statusCode, body, contentType) {
626
626
  });
627
627
  res.end(body);
628
628
  }
629
- function sendBytes(res, statusCode, body, headers) {
629
+ function sendBytes(res, statusCode, body, headers, contentLength = body.length) {
630
630
  const responseHeaders = {
631
631
  ...bridgeCorsHeaders(res),
632
+ "content-length": String(contentLength),
632
633
  };
633
634
  for (const name of [
634
635
  "content-type",
@@ -664,6 +665,23 @@ function readHeader(req, name) {
664
665
  const value = req.headers[name];
665
666
  return typeof value === "string" ? value : "";
666
667
  }
668
+ /**
669
+ * Preserve only the browser request metadata a local dev server needs to
670
+ * classify Vite source-module and stylesheet requests. Agent Native's dev
671
+ * gateway intentionally varies source-file handling by `Sec-Fetch-Dest`; if
672
+ * the bridge drops it, React Router module URLs such as `/app/root.tsx` fall
673
+ * through to Nitro and 404. Keep this allowlist narrow: cookies, authorization,
674
+ * bridge tokens, origins, and referrers must never be forwarded upstream.
675
+ */
676
+ function previewProxyRequestHeaders(req) {
677
+ const headers = {};
678
+ for (const name of ["accept", "sec-fetch-dest"]) {
679
+ const value = readHeader(req, name);
680
+ if (value)
681
+ headers[name] = value;
682
+ }
683
+ return headers;
684
+ }
667
685
  function resolvePreviewSnapshotUrl(devServerUrl, rawUrl) {
668
686
  const base = normalizeHttpUrl(devServerUrl);
669
687
  const parsed = new URL(rawUrl?.trim() || "/", `${base}/`);
@@ -721,7 +739,7 @@ async function fetchPreviewSnapshot(devServerUrl, targetUrl, redirects = 0) {
721
739
  clearTimeout(timeout);
722
740
  }
723
741
  }
724
- async function fetchPreviewProxyResource(devServerUrl, targetUrl, redirects = 0) {
742
+ async function fetchPreviewProxyResource(devServerUrl, targetUrl, requestHeaders = {}, redirects = 0) {
725
743
  if (redirects > 5) {
726
744
  throw new Error("Too many redirects while proxying preview resource.");
727
745
  }
@@ -732,6 +750,7 @@ async function fetchPreviewProxyResource(devServerUrl, targetUrl, redirects = 0)
732
750
  method: "GET",
733
751
  redirect: "manual",
734
752
  signal: controller.signal,
753
+ headers: requestHeaders,
735
754
  });
736
755
  const location = response.headers.get("location");
737
756
  if (location && response.status >= 300 && response.status < 400) {
@@ -740,7 +759,7 @@ async function fetchPreviewProxyResource(devServerUrl, targetUrl, redirects = 0)
740
759
  if (!sameOrigin(redirected.toString(), devServerUrl)) {
741
760
  throw new Error("Proxy redirect left the connected dev server.");
742
761
  }
743
- return fetchPreviewProxyResource(devServerUrl, redirected.toString(), redirects + 1);
762
+ return fetchPreviewProxyResource(devServerUrl, redirected.toString(), requestHeaders, redirects + 1);
744
763
  }
745
764
  return {
746
765
  url: response.url || targetUrl,
@@ -867,6 +886,175 @@ async function assertPathInside(rootPath, targetPath) {
867
886
  }
868
887
  }
869
888
  }
889
+ /**
890
+ * Resolve a bridge file to a stable lock key without following a leaf
891
+ * symlink. Missing parent directories are represented beneath their nearest
892
+ * existing real ancestor, so aliases through in-root directory symlinks share
893
+ * one mutex while first-time file creation remains supported.
894
+ */
895
+ async function resolveSafeBridgeFileTarget(rootPath, targetPath) {
896
+ await assertPathInside(rootPath, targetPath);
897
+ const resolvedRoot = await fs.realpath(rootPath);
898
+ const absolutePath = path.resolve(rootPath, targetPath);
899
+ let existingAncestor = path.dirname(absolutePath);
900
+ let resolvedAncestor = null;
901
+ for (let depth = 0; depth < 32; depth += 1) {
902
+ try {
903
+ resolvedAncestor = await fs.realpath(existingAncestor);
904
+ break;
905
+ }
906
+ catch {
907
+ const parent = path.dirname(existingAncestor);
908
+ if (parent === existingAncestor)
909
+ break;
910
+ existingAncestor = parent;
911
+ }
912
+ }
913
+ if (!resolvedAncestor) {
914
+ throw new Error(`Cannot resolve parent directory: ${absolutePath}`);
915
+ }
916
+ // Existing regular files get their own realpath as the lock key. Besides
917
+ // resolving in-root directory aliases, this canonicalizes case on the
918
+ // default macOS filesystem so `Button.tsx` and `button.tsx` cannot acquire
919
+ // separate mutexes for the same inode. assertPathInside already rejected a
920
+ // symlink leaf before this point.
921
+ const canonicalPath = (await fs.realpath(absolutePath).catch(() => null)) ??
922
+ path.resolve(resolvedAncestor, path.relative(existingAncestor, absolutePath));
923
+ if (canonicalPath !== resolvedRoot &&
924
+ !canonicalPath.startsWith(resolvedRoot + path.sep)) {
925
+ throw new Error("Path traversal detected: resolved target is outside root");
926
+ }
927
+ return { absolutePath, canonicalPath };
928
+ }
929
+ const bridgeWriteLocks = new Map();
930
+ /** Serialize read-check-write sequences for one canonical local file. */
931
+ async function withBridgeWriteLock(canonicalPath, work) {
932
+ const previous = bridgeWriteLocks.get(canonicalPath) ?? Promise.resolve();
933
+ let release;
934
+ const gate = new Promise((resolve) => {
935
+ release = resolve;
936
+ });
937
+ const queued = previous.catch(() => undefined).then(() => gate);
938
+ bridgeWriteLocks.set(canonicalPath, queued);
939
+ await previous.catch(() => undefined);
940
+ try {
941
+ return await work();
942
+ }
943
+ finally {
944
+ release();
945
+ if (bridgeWriteLocks.get(canonicalPath) === queued) {
946
+ bridgeWriteLocks.delete(canonicalPath);
947
+ }
948
+ }
949
+ }
950
+ function contentVersionHash(content) {
951
+ return crypto.createHash("sha256").update(content).digest("hex");
952
+ }
953
+ /** Read an existing regular file without ever following a leaf symlink. */
954
+ async function readBridgeFileSnapshot(absolutePath) {
955
+ const noFollow = fsSync.constants.O_NOFOLLOW ?? 0;
956
+ let handle = null;
957
+ try {
958
+ handle = await fs.open(absolutePath, fsSync.constants.O_RDONLY | noFollow);
959
+ const stat = await handle.stat();
960
+ if (!stat.isFile()) {
961
+ throw new Error(`Bridge target is not a regular file: ${absolutePath}`);
962
+ }
963
+ const bytes = await handle.readFile();
964
+ return {
965
+ content: bytes.toString("utf8"),
966
+ versionHash: contentVersionHash(bytes),
967
+ mode: stat.mode & 0o777,
968
+ };
969
+ }
970
+ catch (error) {
971
+ const code = error instanceof Error && "code" in error
972
+ ? error.code
973
+ : undefined;
974
+ if (code === "ENOENT")
975
+ return null;
976
+ throw error;
977
+ }
978
+ finally {
979
+ await handle?.close().catch(() => undefined);
980
+ }
981
+ }
982
+ class BridgeVersionConflictError extends Error {
983
+ currentVersionHash;
984
+ constructor(currentVersionHash) {
985
+ super("version conflict");
986
+ this.currentVersionHash = currentVersionHash;
987
+ }
988
+ }
989
+ class BridgePreconditionRequiredError extends Error {
990
+ constructor() {
991
+ super("expectedVersionHash is required");
992
+ }
993
+ }
994
+ function assertExpectedBridgeVersion(expectedVersionHash, currentVersionHash, requireExpectedVersionHash = false) {
995
+ // Preserve compatibility: callers that omit a hash retain the existing
996
+ // last-write-wins behavior, including creation. Compiled-source callers opt
997
+ // into exact compare-and-swap by sending the hash returned by read-file.
998
+ if (requireExpectedVersionHash && expectedVersionHash === undefined) {
999
+ throw new BridgePreconditionRequiredError();
1000
+ }
1001
+ if (expectedVersionHash !== undefined &&
1002
+ (currentVersionHash !== undefined
1003
+ ? currentVersionHash !== expectedVersionHash
1004
+ : requireExpectedVersionHash)) {
1005
+ throw new BridgeVersionConflictError(currentVersionHash);
1006
+ }
1007
+ }
1008
+ /**
1009
+ * Replace a file durably without exposing a partial write: create an
1010
+ * O_EXCL/O_NOFOLLOW temp sibling, fsync it, revalidate confinement and the
1011
+ * expected content hash, rename atomically, then fsync the parent directory.
1012
+ */
1013
+ async function atomicWriteBridgeFile(args) {
1014
+ const parent = path.dirname(path.resolve(args.rootPath, args.relPath));
1015
+ await fs.mkdir(parent, { recursive: true });
1016
+ const revalidated = await resolveSafeBridgeFileTarget(args.rootPath, args.relPath);
1017
+ if (revalidated.canonicalPath !== args.lockedCanonicalPath) {
1018
+ throw new Error("Bridge target changed while waiting for the write lock");
1019
+ }
1020
+ const basename = path.basename(revalidated.absolutePath);
1021
+ const tempPath = path.join(parent, `.${basename}.agent-native-${process.pid}-${crypto.randomBytes(8).toString("hex")}.tmp`);
1022
+ const noFollow = fsSync.constants.O_NOFOLLOW ?? 0;
1023
+ let tempHandle = null;
1024
+ try {
1025
+ tempHandle = await fs.open(tempPath, fsSync.constants.O_CREAT |
1026
+ fsSync.constants.O_EXCL |
1027
+ fsSync.constants.O_WRONLY |
1028
+ noFollow, args.originalMode ?? 0o666);
1029
+ await tempHandle.writeFile(args.content, "utf8");
1030
+ await tempHandle.sync();
1031
+ await tempHandle.close();
1032
+ tempHandle = null;
1033
+ // Re-check after the potentially slow temp write/fsync. This catches a
1034
+ // parent/leaf symlink swap and an external content edit before rename.
1035
+ const beforeRenameTarget = await resolveSafeBridgeFileTarget(args.rootPath, args.relPath);
1036
+ if (beforeRenameTarget.canonicalPath !== args.lockedCanonicalPath) {
1037
+ throw new Error("Bridge target changed during atomic write");
1038
+ }
1039
+ const current = await readBridgeFileSnapshot(beforeRenameTarget.absolutePath);
1040
+ assertExpectedBridgeVersion(args.expectedVersionHash, current?.versionHash, args.requireExpectedVersionHash);
1041
+ await fs.rename(tempPath, beforeRenameTarget.absolutePath);
1042
+ const directoryHandle = await fs.open(parent, "r").catch(() => null);
1043
+ if (directoryHandle) {
1044
+ try {
1045
+ await directoryHandle.sync();
1046
+ }
1047
+ finally {
1048
+ await directoryHandle.close();
1049
+ }
1050
+ }
1051
+ return contentVersionHash(args.content);
1052
+ }
1053
+ finally {
1054
+ await tempHandle?.close().catch(() => undefined);
1055
+ await fs.rm(tempPath, { force: true }).catch(() => undefined);
1056
+ }
1057
+ }
870
1058
  /** Allowed file extensions for write/apply-edit operations. */
871
1059
  const ALLOWED_WRITE_EXTENSIONS = new Set([
872
1060
  ".html",
@@ -929,19 +1117,6 @@ function assertNotBlockedSecretPath(relPath) {
929
1117
  throw new Error(`Access rejected: "${relPath}" matches a blocked secret-file pattern.`);
930
1118
  }
931
1119
  }
932
- /**
933
- * Compute a cheap, stable version identifier for a file from its stat: mtime
934
- * plus size. Not a content hash — it is only meant to detect "did this file
935
- * change on disk since the caller last read it", the same tradeoff the inline
936
- * (SQL-backed) workspace provider's versionHash already makes. Returns
937
- * undefined when the file does not exist (new-file case).
938
- */
939
- async function computeVersionHash(absolutePath) {
940
- const stat = await fs.stat(absolutePath).catch(() => null);
941
- if (!stat)
942
- return undefined;
943
- return `${stat.mtimeMs}-${stat.size}`;
944
- }
945
1120
  function countOccurrences(haystack, needle) {
946
1121
  if (!needle)
947
1122
  return 0;
@@ -1236,6 +1411,18 @@ export async function startDesignConnectBridge(manifest, seedOrOptions) {
1236
1411
  // other and boot a frame with another frame's identity. Keep keyed scripts
1237
1412
  // for modern clients while retaining the unkeyed slot for older clients.
1238
1413
  const liveEditBridgeScripts = new Map();
1414
+ // Identifies THIS bridge process's in-memory registry, minted fresh every
1415
+ // time the bridge boots. `liveEditBridgeScripts` above only lives in
1416
+ // process memory, so a bridge restart (crash, machine sleep/wake, manual
1417
+ // restart) silently empties it: any screen that registered a bridgeKey
1418
+ // before the restart now gets a 409 "unknown bridge key" from `/live-edit`
1419
+ // even though nothing about that screen actually changed. Echoing this id
1420
+ // on both the registration response and the 409 lets a client tell the two
1421
+ // cases apart — "this exact process never saw my key" (stale/typo, id
1422
+ // matches what it already has cached) vs. "the process restarted since I
1423
+ // registered" (id changed, safe to transparently re-POST `/live-edit-bridge`
1424
+ // and retry) — instead of guessing from the error text or retrying forever.
1425
+ const bridgeInstanceId = crypto.randomBytes(16).toString("hex");
1239
1426
  const server = http.createServer((req, res) => {
1240
1427
  const corsApproved = configureBridgeCors(req, res, configuredOrigins);
1241
1428
  if (req.method === "OPTIONS") {
@@ -1284,6 +1471,7 @@ export async function startDesignConnectBridge(manifest, seedOrOptions) {
1284
1471
  ok: true,
1285
1472
  source: manifest.source,
1286
1473
  appFingerprint: designConnectAppFingerprint(manifest),
1474
+ bridgeInstanceId,
1287
1475
  });
1288
1476
  return;
1289
1477
  }
@@ -1334,6 +1522,7 @@ export async function startDesignConnectBridge(manifest, seedOrOptions) {
1334
1522
  }
1335
1523
  sendJson(res, 200, {
1336
1524
  ok: true,
1525
+ bridgeInstanceId,
1337
1526
  ...(bridgeKey ? { bridgeKey } : {}),
1338
1527
  });
1339
1528
  }
@@ -1366,8 +1555,17 @@ export async function startDesignConnectBridge(manifest, seedOrOptions) {
1366
1555
  if (includeEditorBridge &&
1367
1556
  requestedBridgeKey &&
1368
1557
  !editorBridgeScript) {
1558
+ // Machine-readable `code` + echoed `bridgeKey`/`bridgeInstanceId`
1559
+ // let a client distinguish "this bridge process restarted since
1560
+ // I last registered — safe to silently re-POST
1561
+ // /live-edit-bridge and retry" from a genuine caller bug,
1562
+ // instead of string-matching `error` (see bridgeInstanceId's
1563
+ // doc comment above for the full rationale).
1369
1564
  sendJson(res, 409, {
1370
1565
  ok: false,
1566
+ code: "unknown-bridge-key",
1567
+ bridgeKey: requestedBridgeKey,
1568
+ bridgeInstanceId,
1371
1569
  error: "The requested live-edit bridge script is not registered. Reload the Design frame to register it again.",
1372
1570
  });
1373
1571
  return;
@@ -1467,155 +1665,154 @@ export async function startDesignConnectBridge(manifest, seedOrOptions) {
1467
1665
  sendJson(res, 400, { ok: false, error: "relPath is required" });
1468
1666
  return;
1469
1667
  }
1470
- await assertPathInside(manifest.rootPath, relPath);
1471
1668
  assertNotBlockedSecretPath(relPath);
1472
- const absolutePath = path.resolve(manifest.rootPath, relPath);
1669
+ const initialTarget = await resolveSafeBridgeFileTarget(manifest.rootPath, relPath);
1473
1670
  if (pathname === "/read-file") {
1474
1671
  // Read-file: no extension restriction (agents need to read any
1475
1672
  // non-secret file), but the secret-path blocklist above still
1476
1673
  // applies to .env*, *.pem, *.key, id_rsa*, and anything under .git/.
1477
- let content;
1478
- try {
1479
- content = await fs.readFile(absolutePath, "utf8");
1480
- }
1481
- catch (err) {
1482
- const code = err instanceof Error &&
1483
- "code" in err &&
1484
- err.code;
1485
- if (code === "ENOENT") {
1486
- sendJson(res, 404, { ok: false, error: "file not found" });
1487
- }
1488
- else {
1489
- sendJson(res, 500, {
1490
- ok: false,
1491
- error: `read failed: ${err instanceof Error ? err.message : String(err)}`,
1492
- });
1493
- }
1674
+ const snapshot = await readBridgeFileSnapshot(initialTarget.absolutePath);
1675
+ if (!snapshot) {
1676
+ sendJson(res, 404, { ok: false, error: "file not found" });
1494
1677
  return;
1495
1678
  }
1496
- const versionHash = await computeVersionHash(absolutePath);
1497
- sendJson(res, 200, { ok: true, content, versionHash });
1679
+ sendJson(res, 200, {
1680
+ ok: true,
1681
+ content: snapshot.content,
1682
+ versionHash: snapshot.versionHash,
1683
+ });
1498
1684
  return;
1499
1685
  }
1500
1686
  // write-file and apply-edit only allow known text/code extensions.
1501
1687
  assertAllowedExtension(relPath);
1502
- // Optional optimistic-concurrency check: when the caller supplies
1503
- // expectedVersionHash, compare it against the file's CURRENT hash
1504
- // before writing. A missing file is treated as no-conflict (new
1505
- // file case) so first-time writes always succeed.
1506
1688
  const expectedVersionHash = typeof body["expectedVersionHash"] === "string"
1507
1689
  ? body["expectedVersionHash"]
1508
1690
  : undefined;
1509
- if (expectedVersionHash !== undefined) {
1510
- const currentVersionHash = await computeVersionHash(absolutePath);
1511
- if (currentVersionHash !== undefined &&
1512
- currentVersionHash !== expectedVersionHash) {
1513
- sendJson(res, 409, {
1514
- ok: false,
1515
- error: "version conflict",
1516
- currentVersionHash,
1691
+ const requireExpectedVersionHash = body["requireExpectedVersionHash"] === true;
1692
+ await withBridgeWriteLock(initialTarget.canonicalPath, async () => {
1693
+ const lockedTarget = await resolveSafeBridgeFileTarget(manifest.rootPath, relPath);
1694
+ if (lockedTarget.canonicalPath !== initialTarget.canonicalPath) {
1695
+ throw new Error("Bridge target changed while waiting for the write lock");
1696
+ }
1697
+ const existing = await readBridgeFileSnapshot(lockedTarget.absolutePath);
1698
+ assertExpectedBridgeVersion(expectedVersionHash, existing?.versionHash, requireExpectedVersionHash);
1699
+ if (pathname === "/write-file") {
1700
+ const content = typeof body["content"] === "string"
1701
+ ? body["content"]
1702
+ : undefined;
1703
+ if (content === undefined) {
1704
+ sendJson(res, 400, {
1705
+ ok: false,
1706
+ error: "content is required for write-file",
1707
+ });
1708
+ return;
1709
+ }
1710
+ const versionHash = await atomicWriteBridgeFile({
1711
+ rootPath: manifest.rootPath,
1712
+ relPath,
1713
+ lockedCanonicalPath: initialTarget.canonicalPath,
1714
+ content,
1715
+ expectedVersionHash,
1716
+ requireExpectedVersionHash,
1717
+ originalMode: existing?.mode,
1517
1718
  });
1719
+ sendJson(res, 200, { ok: true, relPath, versionHash });
1518
1720
  return;
1519
1721
  }
1520
- }
1521
- if (pathname === "/write-file") {
1522
- const content = typeof body["content"] === "string"
1523
- ? body["content"]
1722
+ // /apply-edit supports either full replace ({content}) or one
1723
+ // exact search-and-replace. Both stay within this file's lock.
1724
+ if (typeof body["content"] === "string") {
1725
+ const versionHash = await atomicWriteBridgeFile({
1726
+ rootPath: manifest.rootPath,
1727
+ relPath,
1728
+ lockedCanonicalPath: initialTarget.canonicalPath,
1729
+ content: body["content"],
1730
+ expectedVersionHash,
1731
+ requireExpectedVersionHash,
1732
+ originalMode: existing?.mode,
1733
+ });
1734
+ sendJson(res, 200, {
1735
+ ok: true,
1736
+ relPath,
1737
+ method: "replace",
1738
+ versionHash,
1739
+ });
1740
+ return;
1741
+ }
1742
+ const search = typeof body["search"] === "string" ? body["search"] : undefined;
1743
+ const replace = typeof body["replace"] === "string"
1744
+ ? body["replace"]
1524
1745
  : undefined;
1525
- if (content === undefined) {
1746
+ if (search === undefined || replace === undefined) {
1526
1747
  sendJson(res, 400, {
1527
1748
  ok: false,
1528
- error: "content is required for write-file",
1749
+ error: "apply-edit requires either {content} for a full replace, or {search, replace} for a patch",
1529
1750
  });
1530
1751
  return;
1531
1752
  }
1532
- await fs.mkdir(path.dirname(absolutePath), { recursive: true });
1533
- await fs.writeFile(absolutePath, content, "utf8");
1534
- const versionHash = await computeVersionHash(absolutePath);
1535
- sendJson(res, 200, { ok: true, relPath, versionHash });
1536
- return;
1537
- }
1538
- // /apply-edit: supports either full replace ({content}) or
1539
- // search-and-replace ({search, replace}).
1540
- if (typeof body["content"] === "string") {
1541
- // Full-file replace via apply-edit — same as write-file but keeps
1542
- // the endpoint semantically separate for callers that want to
1543
- // distinguish intent.
1544
- await fs.mkdir(path.dirname(absolutePath), { recursive: true });
1545
- await fs.writeFile(absolutePath, body["content"], "utf8");
1546
- const versionHash = await computeVersionHash(absolutePath);
1547
- sendJson(res, 200, {
1548
- ok: true,
1549
- relPath,
1550
- method: "replace",
1551
- versionHash,
1552
- });
1553
- return;
1554
- }
1555
- const search = typeof body["search"] === "string" ? body["search"] : undefined;
1556
- const replace = typeof body["replace"] === "string" ? body["replace"] : undefined;
1557
- if (search === undefined || replace === undefined) {
1558
- sendJson(res, 400, {
1559
- ok: false,
1560
- error: "apply-edit requires either {content} for a full replace, or {search, replace} for a patch",
1561
- });
1562
- return;
1563
- }
1564
- let existing;
1565
- try {
1566
- existing = await fs.readFile(absolutePath, "utf8");
1567
- }
1568
- catch (err) {
1569
- const code = err instanceof Error &&
1570
- "code" in err &&
1571
- err.code;
1572
- if (code === "ENOENT") {
1753
+ if (!existing) {
1573
1754
  sendJson(res, 404, {
1574
1755
  ok: false,
1575
1756
  error: "file not found — use write-file to create new files",
1576
1757
  });
1758
+ return;
1577
1759
  }
1578
- else {
1579
- sendJson(res, 500, {
1760
+ if (search.length === 0) {
1761
+ sendJson(res, 400, {
1580
1762
  ok: false,
1581
- error: `read failed: ${err instanceof Error ? err.message : String(err)}`,
1763
+ error: "search string must not be empty",
1582
1764
  });
1765
+ return;
1583
1766
  }
1584
- return;
1585
- }
1586
- if (search.length === 0) {
1587
- sendJson(res, 400, {
1588
- ok: false,
1589
- error: "search string must not be empty",
1767
+ const occurrenceCount = countOccurrences(existing.content, search);
1768
+ if (occurrenceCount === 0) {
1769
+ sendJson(res, 422, {
1770
+ ok: false,
1771
+ error: "search string not found in file",
1772
+ });
1773
+ return;
1774
+ }
1775
+ if (occurrenceCount > 1) {
1776
+ sendJson(res, 422, {
1777
+ ok: false,
1778
+ error: "search string is ambiguous; it appears more than once in the file",
1779
+ });
1780
+ return;
1781
+ }
1782
+ const updated = existing.content.replace(search, replace);
1783
+ const versionHash = await atomicWriteBridgeFile({
1784
+ rootPath: manifest.rootPath,
1785
+ relPath,
1786
+ lockedCanonicalPath: initialTarget.canonicalPath,
1787
+ content: updated,
1788
+ expectedVersionHash,
1789
+ requireExpectedVersionHash,
1790
+ originalMode: existing.mode,
1590
1791
  });
1591
- return;
1592
- }
1593
- const occurrenceCount = countOccurrences(existing, search);
1594
- if (occurrenceCount === 0) {
1595
- sendJson(res, 422, {
1792
+ sendJson(res, 200, {
1793
+ ok: true,
1794
+ relPath,
1795
+ method: "patch",
1796
+ versionHash,
1797
+ });
1798
+ });
1799
+ }
1800
+ catch (err) {
1801
+ if (err instanceof BridgePreconditionRequiredError) {
1802
+ sendJson(res, 428, {
1596
1803
  ok: false,
1597
- error: "search string not found in file",
1804
+ error: "expectedVersionHash is required",
1598
1805
  });
1599
1806
  return;
1600
1807
  }
1601
- if (occurrenceCount > 1) {
1602
- sendJson(res, 422, {
1808
+ if (err instanceof BridgeVersionConflictError) {
1809
+ sendJson(res, 409, {
1603
1810
  ok: false,
1604
- error: "search string is ambiguous; it appears more than once in the file",
1811
+ error: "version conflict",
1812
+ currentVersionHash: err.currentVersionHash,
1605
1813
  });
1606
1814
  return;
1607
1815
  }
1608
- const updated = existing.replace(search, replace);
1609
- await fs.writeFile(absolutePath, updated, "utf8");
1610
- const versionHash = await computeVersionHash(absolutePath);
1611
- sendJson(res, 200, {
1612
- ok: true,
1613
- relPath,
1614
- method: "patch",
1615
- versionHash,
1616
- });
1617
- }
1618
- catch (err) {
1619
1816
  sendJson(res, 500, {
1620
1817
  ok: false,
1621
1818
  error: err instanceof Error ? err.message : String(err),
@@ -1636,8 +1833,8 @@ export async function startDesignConnectBridge(manifest, seedOrOptions) {
1636
1833
  void (async () => {
1637
1834
  try {
1638
1835
  const targetUrl = resolvePreviewProxyUrl(manifest.devServerUrl, req.url);
1639
- const proxied = await fetchPreviewProxyResource(manifest.devServerUrl, targetUrl);
1640
- sendBytes(res, proxied.status >= 400 ? proxied.status : 200, req.method === "HEAD" ? Buffer.alloc(0) : proxied.body, proxied.headers);
1836
+ const proxied = await fetchPreviewProxyResource(manifest.devServerUrl, targetUrl, previewProxyRequestHeaders(req));
1837
+ sendBytes(res, proxied.status >= 400 ? proxied.status : 200, req.method === "HEAD" ? Buffer.alloc(0) : proxied.body, proxied.headers, proxied.body.length);
1641
1838
  }
1642
1839
  catch (err) {
1643
1840
  sendJson(res, 400, {
@@ -1657,7 +1854,7 @@ export async function startDesignConnectBridge(manifest, seedOrOptions) {
1657
1854
  resolve();
1658
1855
  });
1659
1856
  });
1660
- return { server, manifest, bridgeToken, previewToken };
1857
+ return { server, manifest, bridgeToken, previewToken, bridgeInstanceId };
1661
1858
  }
1662
1859
  /**
1663
1860
  * Resolve the design app URL from an explicit value or environment variables.
@@ -1786,12 +1983,11 @@ Options:
1786
1983
 
1787
1984
  Element provenance (resolveNodeToFile):
1788
1985
  The design editor can map a selected DOM element back to its source file,
1789
- line, and React component name when the connected app emits provenance
1790
- attributes at build time. Add one of the following to your app's build:
1986
+ line, and component name using one of these provenance sources:
1791
1987
 
1792
- @vitejs/plugin-react with jsxDEV enabled (development mode default):
1793
- Sets data-source-file and data-source-line on each JSX element
1794
- automatically when using the Babel transform.
1988
+ React development builds with jsxDEV enabled (the default):
1989
+ Design reads the selected element's development-only Fiber debug stack.
1990
+ React does not emit data-source-* DOM attributes automatically.
1795
1991
 
1796
1992
  • A Babel source plugin (e.g. babel-plugin-react-source or a custom plugin):
1797
1993
  Emits data-source-file="src/Button.tsx" data-source-line="12"
@@ -1800,7 +1996,8 @@ Element provenance (resolveNodeToFile):
1800
1996
  • data-loc="src/Button.tsx:12:4" shorthand attribute (Babel source convention):
1801
1997
  The bridge parses this as { sourceFile, line, column } automatically.
1802
1998
 
1803
- Without these attributes the editor still works; provenance is simply absent.
1999
+ In production React builds and other runtimes without explicit attributes,
2000
+ the editor still works but exact element provenance may be absent.
1804
2001
  Cross-origin localhost iframes cannot be read regardless of attributes (CSP).`);
1805
2002
  }
1806
2003
  function removeDaemonFlag(argv) {