@agent-native/core 0.92.10 → 0.92.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +23 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +181 -15
  5. package/corpus/core/src/agent/run-manager.ts +31 -4
  6. package/corpus/core/src/agent/run-store.ts +80 -0
  7. package/corpus/core/src/cli/design-connect.ts +433 -151
  8. package/corpus/core/src/cli/skills.ts +14 -0
  9. package/corpus/core/src/client/AssistantChat.tsx +25 -0
  10. package/corpus/core/src/client/MultiTabAssistantChat.tsx +40 -6
  11. package/corpus/core/src/client/agent-chat.ts +165 -1
  12. package/corpus/core/src/client/chat/index.ts +9 -1
  13. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +38 -2
  14. package/corpus/core/src/client/index.ts +5 -0
  15. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +18 -7
  16. package/corpus/core/src/collab/awareness-store.ts +23 -12
  17. package/corpus/core/src/collab/client.ts +16 -5
  18. package/corpus/core/src/collab/index.ts +4 -0
  19. package/corpus/core/src/collab/recent-edits.ts +65 -2
  20. package/corpus/core/src/index.browser.ts +1 -0
  21. package/corpus/core/src/server/agent-chat-plugin.ts +98 -11
  22. package/corpus/core/src/server/builder-design-systems.ts +30 -4
  23. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +1 -1
  24. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +12 -6
  25. package/corpus/templates/clips/AGENTS.md +4 -0
  26. package/corpus/templates/clips/actions/create-recording.ts +2 -0
  27. package/corpus/templates/clips/actions/lib/create-recording-schema.ts +5 -3
  28. package/corpus/templates/clips/actions/request-transcript.ts +102 -33
  29. package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -0
  30. package/corpus/templates/clips/app/components/player/share-dialog.tsx +59 -19
  31. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +48 -4
  32. package/corpus/templates/clips/app/components/player/video-player.tsx +17 -12
  33. package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
  34. package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
  35. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  36. package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
  37. package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
  38. package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
  39. package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
  40. package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
  41. package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
  42. package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
  43. package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
  44. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -37
  45. package/corpus/templates/clips/app/routes/record.tsx +17 -4
  46. package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
  47. package/corpus/templates/clips/changelog/2026-07-09-desktop-popover-sits-closer-to-the-menu-bar.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-07-09-desktop-recording-optimization-progress-stays-visible-until-the-clip-opens.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-09-fixed-large-video-file-uploads-failing-before-their-resumabl.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-07-09-fixed-new-video-uploads-defaulting-to-private.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-07-09-long-recordings-now-get-enough-time-to-download-their-media-.md +6 -0
  52. package/corpus/templates/clips/changelog/2026-07-09-meeting-notes-no-longer-lowers-your-microphone-volume-for-ot.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-07-09-on-mobile-tapping-a-playing-clip-reveals-playback-controls-w.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-09-regenerate-any-clip-transcript-from-its-saved-recording-or-r.md +6 -0
  55. package/corpus/templates/clips/changelog/2026-07-09-slack-previews-now-show-the-clip-length-alongside-its-descri.md +6 -0
  56. package/corpus/templates/clips/desktop/src/app.tsx +12 -2
  57. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +8 -1
  58. package/corpus/templates/clips/desktop/src/lib/recorder.ts +8 -5
  59. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +5 -5
  60. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +3 -0
  61. package/corpus/templates/clips/desktop/src/styles.css +6 -3
  62. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +29 -11
  63. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
  64. package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +103 -21
  65. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +117 -39
  66. package/corpus/templates/clips/server/lib/slack-unfurls.ts +20 -1
  67. package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +10 -4
  68. package/corpus/templates/clips/shared/recording-core.ts +12 -0
  69. package/corpus/templates/content/AGENTS.md +7 -6
  70. package/corpus/templates/content/actions/_document-flush.ts +132 -17
  71. package/corpus/templates/content/actions/_notion-action-utils.ts +15 -0
  72. package/corpus/templates/content/actions/create-and-link-notion-page.ts +2 -0
  73. package/corpus/templates/content/actions/link-notion-page.ts +2 -0
  74. package/corpus/templates/content/actions/pull-document.ts +5 -7
  75. package/corpus/templates/content/actions/pull-notion-page.ts +2 -0
  76. package/corpus/templates/content/actions/push-notion-page.ts +10 -0
  77. package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +2 -0
  78. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +88 -23
  79. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +32 -2
  80. package/corpus/templates/content/app/hooks/use-notion.ts +6 -6
  81. package/corpus/templates/content/app/i18n/zh-TW.ts +1 -0
  82. package/corpus/templates/content/app/i18n-data.ts +18 -0
  83. package/corpus/templates/content/changelog/2026-07-09-notion-conflict-choices-and-version-restores-preserve-docume.md +6 -0
  84. package/corpus/templates/content/server/lib/notion-sync.ts +80 -64
  85. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +29 -2
  86. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +86 -0
  87. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +57 -0
  88. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +11 -0
  89. package/corpus/templates/design/AGENTS.md +45 -10
  90. package/corpus/templates/design/actions/add-localhost-screens.ts +66 -13
  91. package/corpus/templates/design/actions/apply-component-prop-edit.ts +45 -36
  92. package/corpus/templates/design/actions/apply-motion-edit.ts +25 -31
  93. package/corpus/templates/design/actions/apply-shader-fill.ts +29 -42
  94. package/corpus/templates/design/actions/detach-component-instance.ts +337 -0
  95. package/corpus/templates/design/actions/export-design-as-figma-svg.ts +219 -0
  96. package/corpus/templates/design/actions/generate-design.ts +79 -12
  97. package/corpus/templates/design/actions/generate-screens.ts +57 -12
  98. package/corpus/templates/design/actions/get-figma-styles.ts +115 -0
  99. package/corpus/templates/design/actions/go-to-main-component.ts +254 -0
  100. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +4 -3
  101. package/corpus/templates/design/actions/import-design-source.ts +4 -31
  102. package/corpus/templates/design/actions/import-figma-clipboard.ts +134 -0
  103. package/corpus/templates/design/actions/import-figma-frame.ts +95 -0
  104. package/corpus/templates/design/actions/index-design-system-with-builder.ts +16 -1
  105. package/corpus/templates/design/actions/list-design-components.ts +104 -0
  106. package/corpus/templates/design/actions/list-figma-library-assets.ts +1 -24
  107. package/corpus/templates/design/actions/open-component-source.ts +2 -4
  108. package/corpus/templates/design/actions/present-design-variants.ts +412 -16
  109. package/corpus/templates/design/actions/preview-source-edit.ts +14 -1
  110. package/corpus/templates/design/actions/run-design-audit.ts +25 -3
  111. package/corpus/templates/design/actions/show-design-questions.ts +10 -0
  112. package/corpus/templates/design/actions/swap-component-instance.ts +544 -0
  113. package/corpus/templates/design/actions/take-design-screenshot.ts +45 -60
  114. package/corpus/templates/design/actions/write-local-file.ts +25 -1
  115. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +152 -0
  116. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1077 -58
  117. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +5 -5
  118. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +73 -21
  119. package/corpus/templates/design/app/components/design/EditPanel.tsx +16 -11
  120. package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +364 -0
  121. package/corpus/templates/design/app/components/design/LayersPanel.tsx +180 -53
  122. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +0 -2
  123. package/corpus/templates/design/app/components/design/MotionDock.tsx +144 -21
  124. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1152 -167
  125. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +27 -5
  126. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +23 -2
  127. package/corpus/templates/design/app/components/design/TokensPanel.tsx +63 -49
  128. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1293 -54
  129. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +7 -0
  130. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +6 -1
  131. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +23 -6
  132. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +7 -0
  133. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +5 -0
  134. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +14 -2
  135. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +37 -2
  136. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open-guard.ts +25 -0
  137. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +19 -1
  138. package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +43 -5
  139. package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +27 -2
  140. package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +10 -1
  141. package/corpus/templates/design/app/components/design/code-workbench/store.tsx +19 -2
  142. package/corpus/templates/design/app/components/design/design-canvas/annotation-snapshot.ts +265 -0
  143. package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +16 -5
  144. package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +16 -2
  145. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +7 -2
  146. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +3 -1
  147. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +78 -8
  148. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +451 -73
  149. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +12 -4
  150. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +398 -272
  151. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +41 -7
  152. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +16 -3
  153. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +24 -4
  154. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +222 -4
  155. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +171 -78
  156. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +45 -5
  157. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +299 -28
  158. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +120 -150
  159. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +53 -1
  160. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +284 -117
  161. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +81 -0
  162. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +19 -14
  163. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +7 -0
  164. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +155 -0
  165. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +268 -46
  166. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +350 -31
  167. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +13 -4
  168. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +233 -23
  169. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +72 -2
  170. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +51 -11
  171. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +84 -11
  172. package/corpus/templates/design/app/components/design/inspector/index.ts +3 -0
  173. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +552 -0
  174. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +157 -0
  175. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +53 -0
  176. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +184 -0
  177. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +403 -26
  178. package/corpus/templates/design/app/components/design/multi-screen/types.ts +20 -1
  179. package/corpus/templates/design/app/components/design/types.ts +33 -0
  180. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +59 -7
  181. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +119 -17
  182. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +275 -63
  183. package/corpus/templates/design/app/hooks/use-question-flow.ts +14 -3
  184. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +67 -10
  185. package/corpus/templates/design/app/i18n/zh-TW.ts +91 -1
  186. package/corpus/templates/design/app/i18n-data.ts +1217 -10
  187. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +577 -0
  188. package/corpus/templates/design/app/lib/agent-chat.ts +23 -0
  189. package/corpus/templates/design/app/lib/design-import.ts +24 -0
  190. package/corpus/templates/design/app/lib/figma-clipboard.ts +157 -0
  191. package/corpus/templates/design/app/lib/figma-svg-copy.ts +221 -0
  192. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +143 -30
  193. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +15 -0
  194. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +33 -0
  195. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +38 -0
  196. package/corpus/templates/design/app/pages/design-editor/overview-annotation-context.ts +157 -0
  197. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +62 -6
  198. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +245 -0
  199. package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +586 -0
  200. package/corpus/templates/design/app/pages/design-editor/screen-auto-layout.ts +169 -0
  201. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +201 -1
  202. package/corpus/templates/design/changelog/2026-07-09-added-underline-strikethrough-and-text-case-uppercase-lowerc.md +6 -0
  203. package/corpus/templates/design/changelog/2026-07-09-annotate-now-draws-across-the-all-screens-canvas-without-swi.md +6 -0
  204. package/corpus/templates/design/changelog/2026-07-09-auto-layout-children-can-now-be-dragged-into-freeform-screen.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-09-board-shapes-now-stay-visible-and-nest-correctly-across-the-.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-09-component-source-navigation-now-opens-reliably-from-the-insp.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-09-cross-screen-local-react-layer-moves-now-hand-off-exact-sour.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-09-cross-screen-moves-undo-and-redo-now-keep-both-screens-stabl.md +6 -0
  209. package/corpus/templates/design/changelog/2026-07-09-detach-a-component-instance-into-plain-editable-eleme.md +6 -0
  210. package/corpus/templates/design/changelog/2026-07-09-dragging-layers-into-auto-layout-now-respects-ignore-auto-la.md +6 -0
  211. package/corpus/templates/design/changelog/2026-07-09-drawing-annotations-sent-to-the-agent-now-include-a-rendered.md +6 -0
  212. package/corpus/templates/design/changelog/2026-07-09-editor-sidebars-now-use-figma-matched-default-widths-for-a-r.md +6 -0
  213. package/corpus/templates/design/changelog/2026-07-09-empty-frames-and-rectangles-now-expose-full-auto-layout-cont.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-09-export-any-design-screen-as-a-real-vector-svg-for-figma-with.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-09-grid-auto-layout-now-uses-true-responsive-rows-and-columns-w.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-09-hidden-and-locked-screens-now-behave-consistently-between-la.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-09-image-fills-now-preserve-valid-urls-and-layered-fit-settings.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-09-import-figma-frames-as-pixel-accurate-design-screens-via-a-s.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-09-inspect-code-now-hides-internal-styling-metadata-and-wraps-l.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-09-inspector-blur-and-shadow-effects-now-preserve-other-effects.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-09-jump-to-a-components-other-instances-from-the-inspecto.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-09-keyboard-shortcuts-are-now-discoverable-in-a-figma-style-bot.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-09-layer-selection-now-keeps-cmd-or-ctrl-toggles-and-shift-rang.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-09-layers-dropped-into-auto-layout-now-participate-correctly-in.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-09-local-live-edit-screens-now-enter-and-leave-full-view-withou.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-09-local-preview-urls-now-live-in-the-inspector-instead-of-taki.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-can-now-be-hidden-and-locked-from-layers-.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-now-keep-exact-source-provenance-and-supp.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-09-local-react-visual-editing-now-loads-reliably-and-shows-live.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-09-paste-from-figma-cmd-v-now-imports-exact-editable-nodes-when.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-09-pressing-escape-after-selecting-a-screen-now-reveals-page-pr.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-09-right-click-overlapping-or-nested-objects-to-choose-the-exac.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-09-scale-edge-stretch-and-center-constraints-now-preserve-layer.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-09-screen-rows-can-now-be-reordered-in-layers-with-matching-can.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-09-screen-selection-stays-responsive-in-designs-with-hundreds-o.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-09-shift-a-now-enables-auto-layout-on-a-selected-screen-with-fl.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-09-swap-a-component-instance-for-another-from-the-libra.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-10-drawing-annotations-now-stay-available-to-retry-when-the.md +6 -0
  239. package/corpus/templates/design/e2e/global-setup.ts +3 -6
  240. package/corpus/templates/design/e2e/helpers.ts +27 -6
  241. package/corpus/templates/design/server/lib/design-to-figma-svg.ts +1412 -0
  242. package/corpus/templates/design/server/lib/figma-clipboard-match.ts +196 -0
  243. package/corpus/templates/design/server/lib/figma-node-import.ts +311 -0
  244. package/corpus/templates/design/server/lib/figma-node-to-html.ts +1334 -0
  245. package/corpus/templates/design/server/lib/figma-paste-fallback.ts +64 -0
  246. package/corpus/templates/design/server/lib/playwright-runtime.ts +78 -0
  247. package/corpus/templates/design/server/plugins/db.ts +41 -0
  248. package/corpus/templates/design/server/source-workspace.ts +64 -0
  249. package/corpus/templates/design/shared/board-file.ts +13 -2
  250. package/corpus/templates/design/shared/canvas-math.ts +231 -33
  251. package/corpus/templates/design/shared/code-layer.ts +122 -8
  252. package/corpus/templates/design/shared/component-library.ts +161 -0
  253. package/corpus/templates/design/shared/db-conflict.ts +27 -0
  254. package/corpus/templates/design/shared/design-source-capabilities.ts +2 -1
  255. package/corpus/templates/design/shared/figma-url.ts +149 -0
  256. package/corpus/templates/design/shared/pen-path.ts +16 -7
  257. package/corpus/templates/design/shared/source-mode.ts +1 -0
  258. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +7 -19
  259. package/corpus/templates/plan/changelog/2026-07-09-canvas-panning-now-keeps-zoom-fixed-even-at-the-edges.md +6 -0
  260. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +16 -1
  261. package/corpus/templates/slides/app/context/DeckContext.tsx +311 -70
  262. package/corpus/templates/slides/changelog/2026-07-09-fixed-the-slide-rail-going-permanently-stale-during-a-long-a.md +6 -0
  263. package/dist/agent/production-agent.d.ts +79 -6
  264. package/dist/agent/production-agent.d.ts.map +1 -1
  265. package/dist/agent/production-agent.js +161 -14
  266. package/dist/agent/production-agent.js.map +1 -1
  267. package/dist/agent/run-manager.d.ts.map +1 -1
  268. package/dist/agent/run-manager.js +29 -5
  269. package/dist/agent/run-manager.js.map +1 -1
  270. package/dist/agent/run-store.d.ts +46 -0
  271. package/dist/agent/run-store.d.ts.map +1 -1
  272. package/dist/agent/run-store.js +61 -0
  273. package/dist/agent/run-store.js.map +1 -1
  274. package/dist/cli/design-connect.d.ts +6 -0
  275. package/dist/cli/design-connect.d.ts.map +1 -1
  276. package/dist/cli/design-connect.js +336 -139
  277. package/dist/cli/design-connect.js.map +1 -1
  278. package/dist/cli/skills.d.ts.map +1 -1
  279. package/dist/cli/skills.js +14 -0
  280. package/dist/cli/skills.js.map +1 -1
  281. package/dist/client/AssistantChat.d.ts +2 -0
  282. package/dist/client/AssistantChat.d.ts.map +1 -1
  283. package/dist/client/AssistantChat.js +19 -3
  284. package/dist/client/AssistantChat.js.map +1 -1
  285. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  286. package/dist/client/MultiTabAssistantChat.js +38 -18
  287. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  288. package/dist/client/agent-chat.d.ts +60 -0
  289. package/dist/client/agent-chat.d.ts.map +1 -1
  290. package/dist/client/agent-chat.js +122 -1
  291. package/dist/client/agent-chat.js.map +1 -1
  292. package/dist/client/chat/index.d.ts +1 -1
  293. package/dist/client/chat/index.d.ts.map +1 -1
  294. package/dist/client/chat/index.js +1 -1
  295. package/dist/client/chat/index.js.map +1 -1
  296. package/dist/client/extensions/EmbeddedExtension.d.ts +12 -0
  297. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  298. package/dist/client/extensions/EmbeddedExtension.js +36 -2
  299. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  300. package/dist/client/index.d.ts +1 -1
  301. package/dist/client/index.d.ts.map +1 -1
  302. package/dist/client/index.js +1 -1
  303. package/dist/client/index.js.map +1 -1
  304. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  305. package/dist/client/rich-markdown-editor/useCollabReconcile.js +18 -6
  306. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  307. package/dist/collab/awareness-store.d.ts +5 -0
  308. package/dist/collab/awareness-store.d.ts.map +1 -1
  309. package/dist/collab/awareness-store.js +19 -12
  310. package/dist/collab/awareness-store.js.map +1 -1
  311. package/dist/collab/client.d.ts.map +1 -1
  312. package/dist/collab/client.js +13 -5
  313. package/dist/collab/client.js.map +1 -1
  314. package/dist/collab/index.d.ts +1 -0
  315. package/dist/collab/index.d.ts.map +1 -1
  316. package/dist/collab/index.js +1 -0
  317. package/dist/collab/index.js.map +1 -1
  318. package/dist/collab/recent-edits.d.ts +3 -1
  319. package/dist/collab/recent-edits.d.ts.map +1 -1
  320. package/dist/collab/recent-edits.js +57 -2
  321. package/dist/collab/recent-edits.js.map +1 -1
  322. package/dist/index.browser.d.ts +1 -1
  323. package/dist/index.browser.d.ts.map +1 -1
  324. package/dist/index.browser.js +1 -1
  325. package/dist/index.browser.js.map +1 -1
  326. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  327. package/dist/server/agent-chat-plugin.js +87 -10
  328. package/dist/server/agent-chat-plugin.js.map +1 -1
  329. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  330. package/dist/server/builder-design-systems.d.ts +15 -0
  331. package/dist/server/builder-design-systems.d.ts.map +1 -1
  332. package/dist/server/builder-design-systems.js +9 -4
  333. package/dist/server/builder-design-systems.js.map +1 -1
  334. package/package.json +1 -1
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import crypto from "node:crypto";
3
3
  import fsSync from "node:fs";
4
4
  import fs from "node:fs/promises";
5
+ import type { FileHandle } from "node:fs/promises";
5
6
  import http, {
6
7
  type IncomingMessage,
7
8
  type Server,
@@ -123,6 +124,12 @@ export interface DesignConnectBridge {
123
124
  * bridge-registration endpoints. Safe to hand to the Design browser, but
124
125
  * never accepted by filesystem endpoints. */
125
126
  previewToken: string;
127
+ /** Random id minted fresh each time this bridge process boots. Also
128
+ * returned by `/health`, `/live-edit-bridge`, and the "unknown bridge key"
129
+ * 409 from `/live-edit` — a client can compare it across those responses
130
+ * to tell a restarted bridge process apart from a genuine registration
131
+ * bug. See the `bridgeInstanceId` doc comment in startDesignConnectBridge. */
132
+ bridgeInstanceId: string;
126
133
  }
127
134
 
128
135
  export interface DesignConnectBridgeOptions {
@@ -679,11 +686,11 @@ export async function prepareDesignConnectManifest(
679
686
  reason:
680
687
  operation === "resolveNodeToFile"
681
688
  ? // resolveNodeToFile maps a runtime DOM node id (from the editor's
682
- // 'select' payload) to { file, line, component } provenance. The
683
- // bridge endpoint exists; per-element provenance data must be
684
- // emitted by the connected app at build time — see the provenance
685
- // note in the help text below.
686
- "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)."
689
+ // 'select' payload) to { file, line, component } provenance.
690
+ // React development builds expose jsxDEV call sites through the
691
+ // Fiber debug stack; other runtimes/builds can emit explicit DOM
692
+ // provenance attributes — see the help text below.
693
+ "React development builds resolve jsxDEV call sites automatically; other runtimes can emit data-source-file / data-source-line / data-component-name attributes."
687
694
  : undefined,
688
695
  })),
689
696
  };
@@ -786,9 +793,11 @@ function sendBytes(
786
793
  statusCode: number,
787
794
  body: Buffer,
788
795
  headers: Headers,
796
+ contentLength = body.length,
789
797
  ) {
790
798
  const responseHeaders: Record<string, string> = {
791
799
  ...bridgeCorsHeaders(res),
800
+ "content-length": String(contentLength),
792
801
  };
793
802
  for (const name of [
794
803
  "content-type",
@@ -833,6 +842,25 @@ function readHeader(req: IncomingMessage, name: string): string {
833
842
  return typeof value === "string" ? value : "";
834
843
  }
835
844
 
845
+ /**
846
+ * Preserve only the browser request metadata a local dev server needs to
847
+ * classify Vite source-module and stylesheet requests. Agent Native's dev
848
+ * gateway intentionally varies source-file handling by `Sec-Fetch-Dest`; if
849
+ * the bridge drops it, React Router module URLs such as `/app/root.tsx` fall
850
+ * through to Nitro and 404. Keep this allowlist narrow: cookies, authorization,
851
+ * bridge tokens, origins, and referrers must never be forwarded upstream.
852
+ */
853
+ function previewProxyRequestHeaders(
854
+ req: IncomingMessage,
855
+ ): Record<string, string> {
856
+ const headers: Record<string, string> = {};
857
+ for (const name of ["accept", "sec-fetch-dest"] as const) {
858
+ const value = readHeader(req, name);
859
+ if (value) headers[name] = value;
860
+ }
861
+ return headers;
862
+ }
863
+
836
864
  function resolvePreviewSnapshotUrl(
837
865
  devServerUrl: string,
838
866
  rawUrl: string | null,
@@ -917,6 +945,7 @@ async function fetchPreviewSnapshot(
917
945
  async function fetchPreviewProxyResource(
918
946
  devServerUrl: string,
919
947
  targetUrl: string,
948
+ requestHeaders: Record<string, string> = {},
920
949
  redirects = 0,
921
950
  ): Promise<{
922
951
  url: string;
@@ -934,6 +963,7 @@ async function fetchPreviewProxyResource(
934
963
  method: "GET",
935
964
  redirect: "manual",
936
965
  signal: controller.signal,
966
+ headers: requestHeaders,
937
967
  });
938
968
  const location = response.headers.get("location");
939
969
  if (location && response.status >= 300 && response.status < 400) {
@@ -945,6 +975,7 @@ async function fetchPreviewProxyResource(
945
975
  return fetchPreviewProxyResource(
946
976
  devServerUrl,
947
977
  redirected.toString(),
978
+ requestHeaders,
948
979
  redirects + 1,
949
980
  );
950
981
  }
@@ -1101,6 +1132,236 @@ async function assertPathInside(
1101
1132
  }
1102
1133
  }
1103
1134
 
1135
+ interface SafeBridgeFileTarget {
1136
+ absolutePath: string;
1137
+ canonicalPath: string;
1138
+ }
1139
+
1140
+ /**
1141
+ * Resolve a bridge file to a stable lock key without following a leaf
1142
+ * symlink. Missing parent directories are represented beneath their nearest
1143
+ * existing real ancestor, so aliases through in-root directory symlinks share
1144
+ * one mutex while first-time file creation remains supported.
1145
+ */
1146
+ async function resolveSafeBridgeFileTarget(
1147
+ rootPath: string,
1148
+ targetPath: string,
1149
+ ): Promise<SafeBridgeFileTarget> {
1150
+ await assertPathInside(rootPath, targetPath);
1151
+ const resolvedRoot = await fs.realpath(rootPath);
1152
+ const absolutePath = path.resolve(rootPath, targetPath);
1153
+ let existingAncestor = path.dirname(absolutePath);
1154
+ let resolvedAncestor: string | null = null;
1155
+ for (let depth = 0; depth < 32; depth += 1) {
1156
+ try {
1157
+ resolvedAncestor = await fs.realpath(existingAncestor);
1158
+ break;
1159
+ } catch {
1160
+ const parent = path.dirname(existingAncestor);
1161
+ if (parent === existingAncestor) break;
1162
+ existingAncestor = parent;
1163
+ }
1164
+ }
1165
+ if (!resolvedAncestor) {
1166
+ throw new Error(`Cannot resolve parent directory: ${absolutePath}`);
1167
+ }
1168
+ // Existing regular files get their own realpath as the lock key. Besides
1169
+ // resolving in-root directory aliases, this canonicalizes case on the
1170
+ // default macOS filesystem so `Button.tsx` and `button.tsx` cannot acquire
1171
+ // separate mutexes for the same inode. assertPathInside already rejected a
1172
+ // symlink leaf before this point.
1173
+ const canonicalPath =
1174
+ (await fs.realpath(absolutePath).catch(() => null)) ??
1175
+ path.resolve(
1176
+ resolvedAncestor,
1177
+ path.relative(existingAncestor, absolutePath),
1178
+ );
1179
+ if (
1180
+ canonicalPath !== resolvedRoot &&
1181
+ !canonicalPath.startsWith(resolvedRoot + path.sep)
1182
+ ) {
1183
+ throw new Error("Path traversal detected: resolved target is outside root");
1184
+ }
1185
+ return { absolutePath, canonicalPath };
1186
+ }
1187
+
1188
+ const bridgeWriteLocks = new Map<string, Promise<void>>();
1189
+
1190
+ /** Serialize read-check-write sequences for one canonical local file. */
1191
+ async function withBridgeWriteLock<T>(
1192
+ canonicalPath: string,
1193
+ work: () => Promise<T>,
1194
+ ): Promise<T> {
1195
+ const previous = bridgeWriteLocks.get(canonicalPath) ?? Promise.resolve();
1196
+ let release!: () => void;
1197
+ const gate = new Promise<void>((resolve) => {
1198
+ release = resolve;
1199
+ });
1200
+ const queued = previous.catch(() => undefined).then(() => gate);
1201
+ bridgeWriteLocks.set(canonicalPath, queued);
1202
+ await previous.catch(() => undefined);
1203
+ try {
1204
+ return await work();
1205
+ } finally {
1206
+ release();
1207
+ if (bridgeWriteLocks.get(canonicalPath) === queued) {
1208
+ bridgeWriteLocks.delete(canonicalPath);
1209
+ }
1210
+ }
1211
+ }
1212
+
1213
+ interface BridgeFileSnapshot {
1214
+ content: string;
1215
+ versionHash: string;
1216
+ mode: number;
1217
+ }
1218
+
1219
+ function contentVersionHash(content: string | Buffer): string {
1220
+ return crypto.createHash("sha256").update(content).digest("hex");
1221
+ }
1222
+
1223
+ /** Read an existing regular file without ever following a leaf symlink. */
1224
+ async function readBridgeFileSnapshot(
1225
+ absolutePath: string,
1226
+ ): Promise<BridgeFileSnapshot | null> {
1227
+ const noFollow = fsSync.constants.O_NOFOLLOW ?? 0;
1228
+ let handle: FileHandle | null = null;
1229
+ try {
1230
+ handle = await fs.open(absolutePath, fsSync.constants.O_RDONLY | noFollow);
1231
+ const stat = await handle.stat();
1232
+ if (!stat.isFile()) {
1233
+ throw new Error(`Bridge target is not a regular file: ${absolutePath}`);
1234
+ }
1235
+ const bytes = await handle.readFile();
1236
+ return {
1237
+ content: bytes.toString("utf8"),
1238
+ versionHash: contentVersionHash(bytes),
1239
+ mode: stat.mode & 0o777,
1240
+ };
1241
+ } catch (error: unknown) {
1242
+ const code =
1243
+ error instanceof Error && "code" in error
1244
+ ? (error as NodeJS.ErrnoException).code
1245
+ : undefined;
1246
+ if (code === "ENOENT") return null;
1247
+ throw error;
1248
+ } finally {
1249
+ await handle?.close().catch(() => undefined);
1250
+ }
1251
+ }
1252
+
1253
+ class BridgeVersionConflictError extends Error {
1254
+ constructor(readonly currentVersionHash?: string) {
1255
+ super("version conflict");
1256
+ }
1257
+ }
1258
+
1259
+ class BridgePreconditionRequiredError extends Error {
1260
+ constructor() {
1261
+ super("expectedVersionHash is required");
1262
+ }
1263
+ }
1264
+
1265
+ function assertExpectedBridgeVersion(
1266
+ expectedVersionHash: string | undefined,
1267
+ currentVersionHash: string | undefined,
1268
+ requireExpectedVersionHash = false,
1269
+ ): void {
1270
+ // Preserve compatibility: callers that omit a hash retain the existing
1271
+ // last-write-wins behavior, including creation. Compiled-source callers opt
1272
+ // into exact compare-and-swap by sending the hash returned by read-file.
1273
+ if (requireExpectedVersionHash && expectedVersionHash === undefined) {
1274
+ throw new BridgePreconditionRequiredError();
1275
+ }
1276
+ if (
1277
+ expectedVersionHash !== undefined &&
1278
+ (currentVersionHash !== undefined
1279
+ ? currentVersionHash !== expectedVersionHash
1280
+ : requireExpectedVersionHash)
1281
+ ) {
1282
+ throw new BridgeVersionConflictError(currentVersionHash);
1283
+ }
1284
+ }
1285
+
1286
+ /**
1287
+ * Replace a file durably without exposing a partial write: create an
1288
+ * O_EXCL/O_NOFOLLOW temp sibling, fsync it, revalidate confinement and the
1289
+ * expected content hash, rename atomically, then fsync the parent directory.
1290
+ */
1291
+ async function atomicWriteBridgeFile(args: {
1292
+ rootPath: string;
1293
+ relPath: string;
1294
+ lockedCanonicalPath: string;
1295
+ content: string;
1296
+ expectedVersionHash?: string;
1297
+ requireExpectedVersionHash?: boolean;
1298
+ originalMode?: number;
1299
+ }): Promise<string> {
1300
+ const parent = path.dirname(path.resolve(args.rootPath, args.relPath));
1301
+ await fs.mkdir(parent, { recursive: true });
1302
+ const revalidated = await resolveSafeBridgeFileTarget(
1303
+ args.rootPath,
1304
+ args.relPath,
1305
+ );
1306
+ if (revalidated.canonicalPath !== args.lockedCanonicalPath) {
1307
+ throw new Error("Bridge target changed while waiting for the write lock");
1308
+ }
1309
+
1310
+ const basename = path.basename(revalidated.absolutePath);
1311
+ const tempPath = path.join(
1312
+ parent,
1313
+ `.${basename}.agent-native-${process.pid}-${crypto.randomBytes(8).toString("hex")}.tmp`,
1314
+ );
1315
+ const noFollow = fsSync.constants.O_NOFOLLOW ?? 0;
1316
+ let tempHandle: FileHandle | null = null;
1317
+ try {
1318
+ tempHandle = await fs.open(
1319
+ tempPath,
1320
+ fsSync.constants.O_CREAT |
1321
+ fsSync.constants.O_EXCL |
1322
+ fsSync.constants.O_WRONLY |
1323
+ noFollow,
1324
+ args.originalMode ?? 0o666,
1325
+ );
1326
+ await tempHandle.writeFile(args.content, "utf8");
1327
+ await tempHandle.sync();
1328
+ await tempHandle.close();
1329
+ tempHandle = null;
1330
+
1331
+ // Re-check after the potentially slow temp write/fsync. This catches a
1332
+ // parent/leaf symlink swap and an external content edit before rename.
1333
+ const beforeRenameTarget = await resolveSafeBridgeFileTarget(
1334
+ args.rootPath,
1335
+ args.relPath,
1336
+ );
1337
+ if (beforeRenameTarget.canonicalPath !== args.lockedCanonicalPath) {
1338
+ throw new Error("Bridge target changed during atomic write");
1339
+ }
1340
+ const current = await readBridgeFileSnapshot(
1341
+ beforeRenameTarget.absolutePath,
1342
+ );
1343
+ assertExpectedBridgeVersion(
1344
+ args.expectedVersionHash,
1345
+ current?.versionHash,
1346
+ args.requireExpectedVersionHash,
1347
+ );
1348
+
1349
+ await fs.rename(tempPath, beforeRenameTarget.absolutePath);
1350
+ const directoryHandle = await fs.open(parent, "r").catch(() => null);
1351
+ if (directoryHandle) {
1352
+ try {
1353
+ await directoryHandle.sync();
1354
+ } finally {
1355
+ await directoryHandle.close();
1356
+ }
1357
+ }
1358
+ return contentVersionHash(args.content);
1359
+ } finally {
1360
+ await tempHandle?.close().catch(() => undefined);
1361
+ await fs.rm(tempPath, { force: true }).catch(() => undefined);
1362
+ }
1363
+ }
1364
+
1104
1365
  /** Allowed file extensions for write/apply-edit operations. */
1105
1366
  const ALLOWED_WRITE_EXTENSIONS = new Set([
1106
1367
  ".html",
@@ -1166,21 +1427,6 @@ function assertNotBlockedSecretPath(relPath: string): void {
1166
1427
  }
1167
1428
  }
1168
1429
 
1169
- /**
1170
- * Compute a cheap, stable version identifier for a file from its stat: mtime
1171
- * plus size. Not a content hash — it is only meant to detect "did this file
1172
- * change on disk since the caller last read it", the same tradeoff the inline
1173
- * (SQL-backed) workspace provider's versionHash already makes. Returns
1174
- * undefined when the file does not exist (new-file case).
1175
- */
1176
- async function computeVersionHash(
1177
- absolutePath: string,
1178
- ): Promise<string | undefined> {
1179
- const stat = await fs.stat(absolutePath).catch(() => null);
1180
- if (!stat) return undefined;
1181
- return `${stat.mtimeMs}-${stat.size}`;
1182
- }
1183
-
1184
1430
  function countOccurrences(haystack: string, needle: string): number {
1185
1431
  if (!needle) return 0;
1186
1432
  let count = 0;
@@ -1516,6 +1762,18 @@ export async function startDesignConnectBridge(
1516
1762
  // other and boot a frame with another frame's identity. Keep keyed scripts
1517
1763
  // for modern clients while retaining the unkeyed slot for older clients.
1518
1764
  const liveEditBridgeScripts = new Map<string, string>();
1765
+ // Identifies THIS bridge process's in-memory registry, minted fresh every
1766
+ // time the bridge boots. `liveEditBridgeScripts` above only lives in
1767
+ // process memory, so a bridge restart (crash, machine sleep/wake, manual
1768
+ // restart) silently empties it: any screen that registered a bridgeKey
1769
+ // before the restart now gets a 409 "unknown bridge key" from `/live-edit`
1770
+ // even though nothing about that screen actually changed. Echoing this id
1771
+ // on both the registration response and the 409 lets a client tell the two
1772
+ // cases apart — "this exact process never saw my key" (stale/typo, id
1773
+ // matches what it already has cached) vs. "the process restarted since I
1774
+ // registered" (id changed, safe to transparently re-POST `/live-edit-bridge`
1775
+ // and retry) — instead of guessing from the error text or retrying forever.
1776
+ const bridgeInstanceId = crypto.randomBytes(16).toString("hex");
1519
1777
 
1520
1778
  const server = http.createServer(
1521
1779
  (req: IncomingMessage, res: ServerResponse) => {
@@ -1574,6 +1832,7 @@ export async function startDesignConnectBridge(
1574
1832
  ok: true,
1575
1833
  source: manifest.source,
1576
1834
  appFingerprint: designConnectAppFingerprint(manifest),
1835
+ bridgeInstanceId,
1577
1836
  });
1578
1837
  return;
1579
1838
  }
@@ -1628,6 +1887,7 @@ export async function startDesignConnectBridge(
1628
1887
  }
1629
1888
  sendJson(res, 200, {
1630
1889
  ok: true,
1890
+ bridgeInstanceId,
1631
1891
  ...(bridgeKey ? { bridgeKey } : {}),
1632
1892
  });
1633
1893
  } catch (err: unknown) {
@@ -1668,8 +1928,17 @@ export async function startDesignConnectBridge(
1668
1928
  requestedBridgeKey &&
1669
1929
  !editorBridgeScript
1670
1930
  ) {
1931
+ // Machine-readable `code` + echoed `bridgeKey`/`bridgeInstanceId`
1932
+ // let a client distinguish "this bridge process restarted since
1933
+ // I last registered — safe to silently re-POST
1934
+ // /live-edit-bridge and retry" from a genuine caller bug,
1935
+ // instead of string-matching `error` (see bridgeInstanceId's
1936
+ // doc comment above for the full rationale).
1671
1937
  sendJson(res, 409, {
1672
1938
  ok: false,
1939
+ code: "unknown-bridge-key",
1940
+ bridgeKey: requestedBridgeKey,
1941
+ bridgeInstanceId,
1673
1942
  error:
1674
1943
  "The requested live-edit bridge script is not registered. Reload the Design frame to register it again.",
1675
1944
  });
@@ -1794,174 +2063,185 @@ export async function startDesignConnectBridge(
1794
2063
  return;
1795
2064
  }
1796
2065
 
1797
- await assertPathInside(manifest.rootPath, relPath);
1798
2066
  assertNotBlockedSecretPath(relPath);
1799
- const absolutePath = path.resolve(manifest.rootPath, relPath);
2067
+ const initialTarget = await resolveSafeBridgeFileTarget(
2068
+ manifest.rootPath,
2069
+ relPath,
2070
+ );
1800
2071
 
1801
2072
  if (pathname === "/read-file") {
1802
2073
  // Read-file: no extension restriction (agents need to read any
1803
2074
  // non-secret file), but the secret-path blocklist above still
1804
2075
  // applies to .env*, *.pem, *.key, id_rsa*, and anything under .git/.
1805
- let content: string;
1806
- try {
1807
- content = await fs.readFile(absolutePath, "utf8");
1808
- } catch (err: unknown) {
1809
- const code =
1810
- err instanceof Error &&
1811
- "code" in err &&
1812
- (err as NodeJS.ErrnoException).code;
1813
- if (code === "ENOENT") {
1814
- sendJson(res, 404, { ok: false, error: "file not found" });
1815
- } else {
1816
- sendJson(res, 500, {
1817
- ok: false,
1818
- error: `read failed: ${err instanceof Error ? err.message : String(err)}`,
1819
- });
1820
- }
2076
+ const snapshot = await readBridgeFileSnapshot(
2077
+ initialTarget.absolutePath,
2078
+ );
2079
+ if (!snapshot) {
2080
+ sendJson(res, 404, { ok: false, error: "file not found" });
1821
2081
  return;
1822
2082
  }
1823
- const versionHash = await computeVersionHash(absolutePath);
1824
- sendJson(res, 200, { ok: true, content, versionHash });
2083
+ sendJson(res, 200, {
2084
+ ok: true,
2085
+ content: snapshot.content,
2086
+ versionHash: snapshot.versionHash,
2087
+ });
1825
2088
  return;
1826
2089
  }
1827
2090
 
1828
2091
  // write-file and apply-edit only allow known text/code extensions.
1829
2092
  assertAllowedExtension(relPath);
1830
2093
 
1831
- // Optional optimistic-concurrency check: when the caller supplies
1832
- // expectedVersionHash, compare it against the file's CURRENT hash
1833
- // before writing. A missing file is treated as no-conflict (new
1834
- // file case) so first-time writes always succeed.
1835
2094
  const expectedVersionHash =
1836
2095
  typeof body["expectedVersionHash"] === "string"
1837
2096
  ? body["expectedVersionHash"]
1838
2097
  : undefined;
1839
- if (expectedVersionHash !== undefined) {
1840
- const currentVersionHash = await computeVersionHash(absolutePath);
1841
- if (
1842
- currentVersionHash !== undefined &&
1843
- currentVersionHash !== expectedVersionHash
1844
- ) {
1845
- sendJson(res, 409, {
1846
- ok: false,
1847
- error: "version conflict",
1848
- currentVersionHash,
2098
+ const requireExpectedVersionHash =
2099
+ body["requireExpectedVersionHash"] === true;
2100
+ await withBridgeWriteLock(initialTarget.canonicalPath, async () => {
2101
+ const lockedTarget = await resolveSafeBridgeFileTarget(
2102
+ manifest.rootPath,
2103
+ relPath,
2104
+ );
2105
+ if (lockedTarget.canonicalPath !== initialTarget.canonicalPath) {
2106
+ throw new Error(
2107
+ "Bridge target changed while waiting for the write lock",
2108
+ );
2109
+ }
2110
+ const existing = await readBridgeFileSnapshot(
2111
+ lockedTarget.absolutePath,
2112
+ );
2113
+ assertExpectedBridgeVersion(
2114
+ expectedVersionHash,
2115
+ existing?.versionHash,
2116
+ requireExpectedVersionHash,
2117
+ );
2118
+
2119
+ if (pathname === "/write-file") {
2120
+ const content =
2121
+ typeof body["content"] === "string"
2122
+ ? body["content"]
2123
+ : undefined;
2124
+ if (content === undefined) {
2125
+ sendJson(res, 400, {
2126
+ ok: false,
2127
+ error: "content is required for write-file",
2128
+ });
2129
+ return;
2130
+ }
2131
+ const versionHash = await atomicWriteBridgeFile({
2132
+ rootPath: manifest.rootPath,
2133
+ relPath,
2134
+ lockedCanonicalPath: initialTarget.canonicalPath,
2135
+ content,
2136
+ expectedVersionHash,
2137
+ requireExpectedVersionHash,
2138
+ originalMode: existing?.mode,
2139
+ });
2140
+ sendJson(res, 200, { ok: true, relPath, versionHash });
2141
+ return;
2142
+ }
2143
+
2144
+ // /apply-edit supports either full replace ({content}) or one
2145
+ // exact search-and-replace. Both stay within this file's lock.
2146
+ if (typeof body["content"] === "string") {
2147
+ const versionHash = await atomicWriteBridgeFile({
2148
+ rootPath: manifest.rootPath,
2149
+ relPath,
2150
+ lockedCanonicalPath: initialTarget.canonicalPath,
2151
+ content: body["content"],
2152
+ expectedVersionHash,
2153
+ requireExpectedVersionHash,
2154
+ originalMode: existing?.mode,
2155
+ });
2156
+ sendJson(res, 200, {
2157
+ ok: true,
2158
+ relPath,
2159
+ method: "replace",
2160
+ versionHash,
1849
2161
  });
1850
2162
  return;
1851
2163
  }
1852
- }
1853
2164
 
1854
- if (pathname === "/write-file") {
1855
- const content =
1856
- typeof body["content"] === "string"
1857
- ? body["content"]
2165
+ const search =
2166
+ typeof body["search"] === "string" ? body["search"] : undefined;
2167
+ const replace =
2168
+ typeof body["replace"] === "string"
2169
+ ? body["replace"]
1858
2170
  : undefined;
1859
- if (content === undefined) {
2171
+ if (search === undefined || replace === undefined) {
1860
2172
  sendJson(res, 400, {
1861
2173
  ok: false,
1862
- error: "content is required for write-file",
2174
+ error:
2175
+ "apply-edit requires either {content} for a full replace, or {search, replace} for a patch",
1863
2176
  });
1864
2177
  return;
1865
2178
  }
1866
- await fs.mkdir(path.dirname(absolutePath), { recursive: true });
1867
- await fs.writeFile(absolutePath, content, "utf8");
1868
- const versionHash = await computeVersionHash(absolutePath);
1869
- sendJson(res, 200, { ok: true, relPath, versionHash });
1870
- return;
1871
- }
1872
-
1873
- // /apply-edit: supports either full replace ({content}) or
1874
- // search-and-replace ({search, replace}).
1875
- if (typeof body["content"] === "string") {
1876
- // Full-file replace via apply-edit — same as write-file but keeps
1877
- // the endpoint semantically separate for callers that want to
1878
- // distinguish intent.
1879
- await fs.mkdir(path.dirname(absolutePath), { recursive: true });
1880
- await fs.writeFile(
1881
- absolutePath,
1882
- body["content"] as string,
1883
- "utf8",
1884
- );
1885
- const versionHash = await computeVersionHash(absolutePath);
1886
- sendJson(res, 200, {
1887
- ok: true,
1888
- relPath,
1889
- method: "replace",
1890
- versionHash,
1891
- });
1892
- return;
1893
- }
1894
-
1895
- const search =
1896
- typeof body["search"] === "string" ? body["search"] : undefined;
1897
- const replace =
1898
- typeof body["replace"] === "string" ? body["replace"] : undefined;
1899
- if (search === undefined || replace === undefined) {
1900
- sendJson(res, 400, {
1901
- ok: false,
1902
- error:
1903
- "apply-edit requires either {content} for a full replace, or {search, replace} for a patch",
1904
- });
1905
- return;
1906
- }
1907
-
1908
- let existing: string;
1909
- try {
1910
- existing = await fs.readFile(absolutePath, "utf8");
1911
- } catch (err: unknown) {
1912
- const code =
1913
- err instanceof Error &&
1914
- "code" in err &&
1915
- (err as NodeJS.ErrnoException).code;
1916
- if (code === "ENOENT") {
2179
+ if (!existing) {
1917
2180
  sendJson(res, 404, {
1918
2181
  ok: false,
1919
2182
  error: "file not found — use write-file to create new files",
1920
2183
  });
1921
- } else {
1922
- sendJson(res, 500, {
2184
+ return;
2185
+ }
2186
+ if (search.length === 0) {
2187
+ sendJson(res, 400, {
1923
2188
  ok: false,
1924
- error: `read failed: ${err instanceof Error ? err.message : String(err)}`,
2189
+ error: "search string must not be empty",
1925
2190
  });
2191
+ return;
1926
2192
  }
1927
- return;
1928
- }
1929
-
1930
- if (search.length === 0) {
1931
- sendJson(res, 400, {
1932
- ok: false,
1933
- error: "search string must not be empty",
2193
+ const occurrenceCount = countOccurrences(
2194
+ existing.content,
2195
+ search,
2196
+ );
2197
+ if (occurrenceCount === 0) {
2198
+ sendJson(res, 422, {
2199
+ ok: false,
2200
+ error: "search string not found in file",
2201
+ });
2202
+ return;
2203
+ }
2204
+ if (occurrenceCount > 1) {
2205
+ sendJson(res, 422, {
2206
+ ok: false,
2207
+ error:
2208
+ "search string is ambiguous; it appears more than once in the file",
2209
+ });
2210
+ return;
2211
+ }
2212
+ const updated = existing.content.replace(search, replace);
2213
+ const versionHash = await atomicWriteBridgeFile({
2214
+ rootPath: manifest.rootPath,
2215
+ relPath,
2216
+ lockedCanonicalPath: initialTarget.canonicalPath,
2217
+ content: updated,
2218
+ expectedVersionHash,
2219
+ requireExpectedVersionHash,
2220
+ originalMode: existing.mode,
1934
2221
  });
1935
- return;
1936
- }
1937
-
1938
- const occurrenceCount = countOccurrences(existing, search);
1939
- if (occurrenceCount === 0) {
1940
- sendJson(res, 422, {
2222
+ sendJson(res, 200, {
2223
+ ok: true,
2224
+ relPath,
2225
+ method: "patch",
2226
+ versionHash,
2227
+ });
2228
+ });
2229
+ } catch (err: unknown) {
2230
+ if (err instanceof BridgePreconditionRequiredError) {
2231
+ sendJson(res, 428, {
1941
2232
  ok: false,
1942
- error: "search string not found in file",
2233
+ error: "expectedVersionHash is required",
1943
2234
  });
1944
2235
  return;
1945
2236
  }
1946
- if (occurrenceCount > 1) {
1947
- sendJson(res, 422, {
2237
+ if (err instanceof BridgeVersionConflictError) {
2238
+ sendJson(res, 409, {
1948
2239
  ok: false,
1949
- error:
1950
- "search string is ambiguous; it appears more than once in the file",
2240
+ error: "version conflict",
2241
+ currentVersionHash: err.currentVersionHash,
1951
2242
  });
1952
2243
  return;
1953
2244
  }
1954
-
1955
- const updated = existing.replace(search, replace);
1956
- await fs.writeFile(absolutePath, updated, "utf8");
1957
- const versionHash = await computeVersionHash(absolutePath);
1958
- sendJson(res, 200, {
1959
- ok: true,
1960
- relPath,
1961
- method: "patch",
1962
- versionHash,
1963
- });
1964
- } catch (err: unknown) {
1965
2245
  sendJson(res, 500, {
1966
2246
  ok: false,
1967
2247
  error: err instanceof Error ? err.message : String(err),
@@ -1990,12 +2270,14 @@ export async function startDesignConnectBridge(
1990
2270
  const proxied = await fetchPreviewProxyResource(
1991
2271
  manifest.devServerUrl,
1992
2272
  targetUrl,
2273
+ previewProxyRequestHeaders(req),
1993
2274
  );
1994
2275
  sendBytes(
1995
2276
  res,
1996
2277
  proxied.status >= 400 ? proxied.status : 200,
1997
2278
  req.method === "HEAD" ? Buffer.alloc(0) : proxied.body,
1998
2279
  proxied.headers,
2280
+ proxied.body.length,
1999
2281
  );
2000
2282
  } catch (err: unknown) {
2001
2283
  sendJson(res, 400, {
@@ -2019,7 +2301,7 @@ export async function startDesignConnectBridge(
2019
2301
  });
2020
2302
  });
2021
2303
 
2022
- return { server, manifest, bridgeToken, previewToken };
2304
+ return { server, manifest, bridgeToken, previewToken, bridgeInstanceId };
2023
2305
  }
2024
2306
 
2025
2307
  /**
@@ -2163,12 +2445,11 @@ Options:
2163
2445
 
2164
2446
  Element provenance (resolveNodeToFile):
2165
2447
  The design editor can map a selected DOM element back to its source file,
2166
- line, and React component name when the connected app emits provenance
2167
- attributes at build time. Add one of the following to your app's build:
2448
+ line, and component name using one of these provenance sources:
2168
2449
 
2169
- @vitejs/plugin-react with jsxDEV enabled (development mode default):
2170
- Sets data-source-file and data-source-line on each JSX element
2171
- automatically when using the Babel transform.
2450
+ React development builds with jsxDEV enabled (the default):
2451
+ Design reads the selected element's development-only Fiber debug stack.
2452
+ React does not emit data-source-* DOM attributes automatically.
2172
2453
 
2173
2454
  • A Babel source plugin (e.g. babel-plugin-react-source or a custom plugin):
2174
2455
  Emits data-source-file="src/Button.tsx" data-source-line="12"
@@ -2177,7 +2458,8 @@ Element provenance (resolveNodeToFile):
2177
2458
  • data-loc="src/Button.tsx:12:4" shorthand attribute (Babel source convention):
2178
2459
  The bridge parses this as { sourceFile, line, column } automatically.
2179
2460
 
2180
- Without these attributes the editor still works; provenance is simply absent.
2461
+ In production React builds and other runtimes without explicit attributes,
2462
+ the editor still works but exact element provenance may be absent.
2181
2463
  Cross-origin localhost iframes cannot be read regardless of attributes (CSP).`);
2182
2464
  }
2183
2465