@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
@@ -244,6 +244,44 @@ export function shouldReplacePreviewAfterVisualStyleCommit(args: {
244
244
  return !args.runtimeApplied && !args.runtimeStyleApplied;
245
245
  }
246
246
 
247
+ export interface OptimisticTextDecorationLineEntry {
248
+ key: string;
249
+ value: string;
250
+ }
251
+
252
+ /**
253
+ * BUG-DOUBLE-TOGGLE-RACE — Cmd+U (toggle underline) / Cmd+Shift+X (toggle
254
+ * strikethrough) commit through the SHORTHAND "textDecoration" property, but
255
+ * commitVisualStyles' synchronous optimistic patch to
256
+ * selectedElement.computedStyles only merges the exact key(s) it committed —
257
+ * it never decomposes "textDecoration" into the LONGHAND
258
+ * "textDecorationLine" the toggle reads to decide its next value.
259
+ * `computedStyles.textDecorationLine` only catches up once the bridge's
260
+ * async getComputedStyle round trip lands. A second toggle press within that
261
+ * window would otherwise recompute nextTextDecorationLineValue from the
262
+ * STALE pre-toggle value, land on the exact value the first press already
263
+ * committed, and get deduped as a no-op — consecutive toggles silently stop
264
+ * alternating.
265
+ *
266
+ * Resolves which value a toggle handler should treat as "current": a
267
+ * still-fresh optimistic value this same toggle family already recorded for
268
+ * the SAME selected element (`tracked.key === elementKey`) wins over the
269
+ * (possibly stale) computedStyles reading, since the async measurement that
270
+ * would refresh computedStyles may not have landed yet. A different/new
271
+ * element key (or no tracked entry) falls back to computedStyles, so newly
272
+ * selecting a different element always starts from ITS OWN real state.
273
+ */
274
+ export function resolveOptimisticTextDecorationLine(
275
+ tracked: OptimisticTextDecorationLineEntry | null | undefined,
276
+ elementKey: string | undefined,
277
+ computedTextDecorationLine: string | undefined,
278
+ ): string | undefined {
279
+ if (tracked && elementKey !== undefined && tracked.key === elementKey) {
280
+ return tracked.value;
281
+ }
282
+ return computedTextDecorationLine;
283
+ }
284
+
247
285
  /**
248
286
  * PF12: decide whether a style change from EditPanel (ScrubInput scrub tick /
249
287
  * DesignColorPicker drag tick) should skip the expensive source commit
@@ -0,0 +1,157 @@
1
+ import type { DrawAnnotation } from "@/components/visual-editor/DrawOverlay";
2
+
3
+ export interface OverviewAnnotationScreen {
4
+ id: string;
5
+ name: string;
6
+ x: number;
7
+ y: number;
8
+ width: number;
9
+ height: number;
10
+ }
11
+
12
+ export interface OverviewAnnotationViewportMap {
13
+ width: number;
14
+ height: number;
15
+ zoom: number;
16
+ screens: OverviewAnnotationScreen[];
17
+ board?: { x: number; y: number; width: number; height: number };
18
+ }
19
+
20
+ function escapeSelector(value: string) {
21
+ return typeof CSS !== "undefined" && typeof CSS.escape === "function"
22
+ ? CSS.escape(value)
23
+ : value.replace(/["\\]/g, "\\$&");
24
+ }
25
+
26
+ export function collectOverviewAnnotationViewportMap({
27
+ container,
28
+ screens,
29
+ zoom,
30
+ }: {
31
+ container: HTMLElement;
32
+ screens: readonly { id: string; name: string }[];
33
+ zoom: number;
34
+ }): OverviewAnnotationViewportMap {
35
+ const containerRect = container.getBoundingClientRect();
36
+ const relativeRect = (rect: DOMRect) => ({
37
+ x: rect.left - containerRect.left,
38
+ y: rect.top - containerRect.top,
39
+ width: rect.width,
40
+ height: rect.height,
41
+ });
42
+ const mappedScreens = screens.flatMap((screen) => {
43
+ const shell = container.querySelector<HTMLElement>(
44
+ `[data-frame-id="${escapeSelector(screen.id)}"]`,
45
+ );
46
+ return shell
47
+ ? [
48
+ {
49
+ id: screen.id,
50
+ name: screen.name,
51
+ ...relativeRect(shell.getBoundingClientRect()),
52
+ },
53
+ ]
54
+ : [];
55
+ });
56
+ const boardSurface = container.querySelector<HTMLElement>(
57
+ "[data-board-surface-layer]",
58
+ );
59
+ return {
60
+ width: containerRect.width,
61
+ height: containerRect.height,
62
+ zoom,
63
+ screens: mappedScreens,
64
+ board: boardSurface
65
+ ? relativeRect(boardSurface.getBoundingClientRect())
66
+ : undefined,
67
+ };
68
+ }
69
+
70
+ function annotationBounds(annotation: DrawAnnotation) {
71
+ if (annotation.type === "text") {
72
+ return {
73
+ left: annotation.position.x,
74
+ top: annotation.position.y,
75
+ right: annotation.position.x,
76
+ bottom: annotation.position.y,
77
+ };
78
+ }
79
+ const values = Array.from(
80
+ annotation.pathData?.matchAll(/-?\d+(?:\.\d+)?/g) ?? [],
81
+ (match) => Number(match[0]),
82
+ );
83
+ const xs = values.filter((_, index) => index % 2 === 0);
84
+ const ys = values.filter((_, index) => index % 2 === 1);
85
+ if (!xs.length || !ys.length) return null;
86
+ return {
87
+ left: Math.min(...xs),
88
+ top: Math.min(...ys),
89
+ right: Math.max(...xs),
90
+ bottom: Math.max(...ys),
91
+ };
92
+ }
93
+
94
+ function mappedAnnotationRegions(
95
+ annotation: DrawAnnotation,
96
+ viewportMap: OverviewAnnotationViewportMap,
97
+ ) {
98
+ const bounds = annotationBounds(annotation);
99
+ if (!bounds) return ["board"];
100
+ const screens = viewportMap.screens.filter(
101
+ (screen) =>
102
+ bounds.right >= screen.x &&
103
+ bounds.left <= screen.x + screen.width &&
104
+ bounds.bottom >= screen.y &&
105
+ bounds.top <= screen.y + screen.height,
106
+ );
107
+ return screens.length
108
+ ? screens.map((screen) => `${screen.name} (${screen.id})`)
109
+ : ["board outside screens"];
110
+ }
111
+
112
+ export function formatOverviewAnnotationMessage({
113
+ designId,
114
+ designTitle,
115
+ annotations,
116
+ instruction,
117
+ viewportMap,
118
+ }: {
119
+ designId: string;
120
+ designTitle?: string;
121
+ annotations: DrawAnnotation[];
122
+ instruction: string;
123
+ viewportMap: OverviewAnnotationViewportMap;
124
+ }) {
125
+ const drawing = annotations
126
+ .map((annotation) => {
127
+ const regions = mappedAnnotationRegions(annotation, viewportMap).join(
128
+ ", ",
129
+ );
130
+ return annotation.type === "path"
131
+ ? `[stroke regions=${regions} color=${annotation.color} w=${annotation.lineWidth}] ${annotation.pathData}`
132
+ : `[label regions=${regions} text="${annotation.text ?? ""}" at ${annotation.position.x.toFixed(0)},${annotation.position.y.toFixed(0)}]`;
133
+ })
134
+ .join("\n");
135
+ const screenMap = viewportMap.screens
136
+ .map(
137
+ (screen) =>
138
+ `- ${screen.name} (${screen.id}): x=${screen.x.toFixed(1)}, y=${screen.y.toFixed(1)}, width=${screen.width.toFixed(1)}, height=${screen.height.toFixed(1)}`,
139
+ )
140
+ .join("\n");
141
+ const boardMap = viewportMap.board
142
+ ? `Board surface: x=${viewportMap.board.x.toFixed(1)}, y=${viewportMap.board.y.toFixed(1)}, width=${viewportMap.board.width.toFixed(1)}, height=${viewportMap.board.height.toFixed(1)}`
143
+ : "Board surface: the viewport outside the mapped screen rectangles";
144
+
145
+ return [
146
+ `[Annotations on the all-screens canvas for design ${designId}${designTitle ? ` (${designTitle})` : ""}]`,
147
+ `Overview viewport: ${viewportMap.width.toFixed(0)}x${viewportMap.height.toFixed(0)} at ${viewportMap.zoom.toFixed(1)}% zoom`,
148
+ "Coordinates and screen bounds are viewport-relative at send time; their x/y positions encode the current canvas pan.",
149
+ "",
150
+ "[Screen map]",
151
+ screenMap || "- No screen rectangles were visible",
152
+ boardMap,
153
+ ...(drawing ? ["", "[Drawing]", drawing] : []),
154
+ "",
155
+ instruction || "Apply these annotations to the design.",
156
+ ].join("\n");
157
+ }
@@ -76,9 +76,11 @@ export function getScreenFrameOriginCanvas(args: {
76
76
  * `getInitialFrameGeometry(index, ...)` fallback `getSelectedScreenGeometryForInspector`
77
77
  * and `getScreenFrameOriginCanvas` above already use — as `FrameEntry[]` for
78
78
  * `@shared/canvas-math` bounds/fit helpers (`getFrameGroupBounds`,
79
- * `getCameraForBounds`). Optionally includes the board frame (real Figma has
80
- * no board-file equivalent, but a design with only board primitives and no
81
- * screens should still have something to fit to).
79
+ * `getCameraForBounds`). Optionally includes the bounds of actual board
80
+ * content. This must never receive the logical board hit-test surface: that
81
+ * surface is intentionally enormous and including it would make zoom-to-fit
82
+ * and adjacent-screen placement behave as though the design were 131,072px
83
+ * wide even when the board is empty.
82
84
  */
83
85
  export function getAllScreenFrameEntries(args: {
84
86
  overviewScreens: Array<{
@@ -87,7 +89,7 @@ export function getAllScreenFrameEntries(args: {
87
89
  height?: number;
88
90
  }>;
89
91
  canvasFrameGeometryById: CanvasFrameGeometryById;
90
- boardFrameGeometry?: FrameGeometry;
92
+ boardContentBounds?: FrameGeometry | null;
91
93
  boardFileId?: string | null;
92
94
  }): FrameEntry[] {
93
95
  const entries: FrameEntry[] = args.overviewScreens.map((screen, index) => {
@@ -103,10 +105,10 @@ export function getAllScreenFrameEntries(args: {
103
105
  });
104
106
  if (
105
107
  args.boardFileId &&
106
- args.boardFrameGeometry &&
108
+ args.boardContentBounds &&
107
109
  !entries.some((entry) => entry.id === args.boardFileId)
108
110
  ) {
109
- entries.push({ id: args.boardFileId, geometry: args.boardFrameGeometry });
111
+ entries.push({ id: args.boardFileId, geometry: args.boardContentBounds });
110
112
  }
111
113
  return entries;
112
114
  }
@@ -436,3 +438,57 @@ export function shouldPopToOverviewOnZoomChange(args: {
436
438
  if (args.suppressExplicitZoom) return false;
437
439
  return shouldPopToOverviewOnZoomOut(args);
438
440
  }
441
+
442
+ /**
443
+ * PASTE-HERE-IN-CONTENT: converts a viewport client point into a
444
+ * screen/iframe-local canvas point, given that iframe's own
445
+ * boundingClientRect and the zoom percent (0-100) currently scaling it.
446
+ *
447
+ * Shared by DesignEditor's getContextCanvasPoint (the background/board
448
+ * right-click path, reached through CanvasContextMenu's own
449
+ * onContextMenuCapture) and handleIframeContextMenu (a right-click that
450
+ * lands ON rendered screen content — an element, or empty in-screen space —
451
+ * which opens the menu imperatively via a ref and so bypasses
452
+ * onContextMenuCapture, and with it getCanvasPoint, entirely). Before this
453
+ * was wired into handleIframeContextMenu too, "Paste here" triggered from a
454
+ * right-click on actual screen content never got a canvasX/canvasY at all
455
+ * and silently fell back to the position-less cascade/offset paste instead
456
+ * of landing under the cursor — only a right-click on the empty canvas
457
+ * background (which still goes through onContextMenuCapture) worked.
458
+ *
459
+ * Returns null when there's no iframe rect to measure against or the zoom
460
+ * factor is non-positive (would divide by zero / invert the sign), matching
461
+ * "no reliable canvas point" — callers fall back to their own
462
+ * container-relative or position-less behavior in that case.
463
+ */
464
+ export function computeIframeLocalCanvasPoint(args: {
465
+ clientX: number;
466
+ clientY: number;
467
+ iframeRect: { left: number; top: number } | null | undefined;
468
+ zoomPercent: number;
469
+ }): { x: number; y: number } | null {
470
+ if (!args.iframeRect || !(args.zoomPercent > 0)) return null;
471
+ const factor = args.zoomPercent / 100;
472
+ return {
473
+ x: Math.max(0, (args.clientX - args.iframeRect.left) / factor),
474
+ y: Math.max(0, (args.clientY - args.iframeRect.top) / factor),
475
+ };
476
+ }
477
+
478
+ /**
479
+ * Reads MultiScreenCanvas's live imperative zoom from the exact inline
480
+ * transform written by `applyViewToDom` while a wheel/pinch gesture is still
481
+ * settling. React's `overviewCanvasZoom` intentionally lags those per-frame
482
+ * DOM writes until the gesture commit, so cursor-sensitive commands must use
483
+ * this value when it is available.
484
+ */
485
+ export function readOverviewZoomPercentFromTransform(
486
+ transform: string | null | undefined,
487
+ fallbackZoomPercent: number,
488
+ ): number {
489
+ const match = transform?.match(/(?:^|\s)scale\(\s*([-+]?\d*\.?\d+)\s*\)/i);
490
+ const scale = match ? Number(match[1]) : Number.NaN;
491
+ return Number.isFinite(scale) && scale > 0
492
+ ? scale * 100
493
+ : fallbackZoomPercent;
494
+ }
@@ -18,6 +18,12 @@ import {
18
18
 
19
19
  import type { ElementInfo } from "@/components/design/types";
20
20
 
21
+ import {
22
+ buildReactSemanticHandoff,
23
+ redactReactSourceAnchor,
24
+ type ReactSourceAnchor,
25
+ type ReactSourceScope,
26
+ } from "./react-semantic-handoff";
21
27
  import { camelStyleProperty } from "./style-utils";
22
28
 
23
29
  export interface PendingVisualStyleEdit {
@@ -26,6 +32,7 @@ export interface PendingVisualStyleEdit {
26
32
  screenName: string;
27
33
  selector: string;
28
34
  sourceId?: string | null;
35
+ sourceAnchor?: ReactSourceAnchor;
29
36
  tagName?: string | null;
30
37
  classes: string[];
31
38
  styles: Record<string, string>;
@@ -104,6 +111,7 @@ export interface PendingLiveTextEdit {
104
111
  screenName: string;
105
112
  selector: string;
106
113
  sourceId?: string | null;
114
+ sourceAnchor?: ReactSourceAnchor;
107
115
  tagName?: string | null;
108
116
  classes: string[];
109
117
  value: string;
@@ -120,13 +128,154 @@ export interface PendingLiveStructureEdit {
120
128
  screenName: string;
121
129
  selector: string;
122
130
  sourceId?: string | null;
131
+ sourceAnchor?: ReactSourceAnchor;
123
132
  anchorSelector: string;
124
133
  anchorSourceId?: string | null;
134
+ anchorSourceAnchor?: ReactSourceAnchor;
125
135
  placement: "before" | "after" | "inside";
126
136
  requestId?: string;
127
137
  updatedAt: number;
128
138
  }
129
139
 
140
+ /**
141
+ * Convert bridge provenance into a bounded semantic source anchor. Runtime
142
+ * ids remain useful for correlating the live preview, but they are never
143
+ * treated as source identities by the coding-agent handoff.
144
+ */
145
+ interface NormalizedResolvablePath {
146
+ value: string;
147
+ absolute: boolean;
148
+ caseInsensitive: boolean;
149
+ }
150
+
151
+ function normalizeResolvablePath(
152
+ rawValue: string | undefined,
153
+ ): NormalizedResolvablePath | undefined {
154
+ const raw = rawValue?.trim().replace(/\\/g, "/");
155
+ if (!raw || raw.includes("\0")) return undefined;
156
+
157
+ let prefix = "";
158
+ let remainder = raw;
159
+ let absolute = false;
160
+ let caseInsensitive = false;
161
+ const drive = raw.match(/^([a-z]):(\/.*)?$/i);
162
+ if (drive) {
163
+ // `C:foo` is drive-relative and must not be treated as a project path.
164
+ if (!drive[2]?.startsWith("/")) return undefined;
165
+ prefix = `${drive[1]!.toUpperCase()}:/`;
166
+ remainder = drive[2].slice(1);
167
+ absolute = true;
168
+ caseInsensitive = true;
169
+ } else if (raw.startsWith("//")) {
170
+ const [server, share, ...rest] = raw.slice(2).split("/");
171
+ if (!server || !share) return undefined;
172
+ prefix = `//${server}/${share}`;
173
+ remainder = rest.join("/");
174
+ absolute = true;
175
+ caseInsensitive = true;
176
+ } else if (raw.startsWith("/")) {
177
+ prefix = "/";
178
+ remainder = raw.slice(1);
179
+ absolute = true;
180
+ } else if (/^[a-z]+:/i.test(raw)) {
181
+ // URL-like values and unsupported drive-relative paths are not files.
182
+ return undefined;
183
+ }
184
+
185
+ const segments: string[] = [];
186
+ for (const segment of remainder.split("/")) {
187
+ if (!segment || segment === ".") continue;
188
+ if (segment === "..") {
189
+ if (segments.length > 0) {
190
+ segments.pop();
191
+ } else if (!absolute) {
192
+ // A relative path may not escape its unknown project root.
193
+ return undefined;
194
+ }
195
+ continue;
196
+ }
197
+ segments.push(segment);
198
+ }
199
+
200
+ const suffix = segments.join("/");
201
+ const value = absolute
202
+ ? prefix.endsWith("/")
203
+ ? `${prefix}${suffix}`
204
+ : suffix
205
+ ? `${prefix}/${suffix}`
206
+ : prefix
207
+ : suffix;
208
+ if (!value) return undefined;
209
+ return { value, absolute, caseInsensitive };
210
+ }
211
+
212
+ function sourcePathRelativeToRoot(args: {
213
+ sourceFile: string;
214
+ rootPath?: string;
215
+ }): string | undefined {
216
+ const source = normalizeResolvablePath(args.sourceFile);
217
+ if (!source) return undefined;
218
+ if (!source.absolute) return source.value;
219
+
220
+ const root = normalizeResolvablePath(args.rootPath);
221
+ if (!root?.absolute || root.caseInsensitive !== source.caseInsensitive) {
222
+ return undefined;
223
+ }
224
+ const comparableSource = source.caseInsensitive
225
+ ? source.value.toLowerCase()
226
+ : source.value;
227
+ const comparableRoot = root.caseInsensitive
228
+ ? root.value.toLowerCase()
229
+ : root.value;
230
+ const rootPrefix = comparableRoot.endsWith("/")
231
+ ? comparableRoot
232
+ : `${comparableRoot}/`;
233
+ if (!comparableSource.startsWith(rootPrefix)) return undefined;
234
+ const relative = source.value.slice(rootPrefix.length);
235
+ return normalizeResolvablePath(relative)?.value;
236
+ }
237
+
238
+ export function reactSourceAnchorForPendingEdit(args: {
239
+ info?: Pick<ElementInfo, "provenance" | "sourceId" | "selector"> | null;
240
+ id?: string;
241
+ runtimeMultiplicity?: number;
242
+ scope?: ReactSourceScope;
243
+ reason?: string;
244
+ rootPath?: string;
245
+ }): ReactSourceAnchor | undefined {
246
+ const provenance = args.info?.provenance;
247
+ const sourceFile = provenance?.sourceFile?.trim();
248
+ if (!sourceFile || !provenance?.line || !provenance.column) return undefined;
249
+ const runtimeMultiplicity =
250
+ Number.isInteger(args.runtimeMultiplicity) &&
251
+ (args.runtimeMultiplicity ?? 0) > 0
252
+ ? args.runtimeMultiplicity!
253
+ : 1;
254
+ const relPath = sourcePathRelativeToRoot({
255
+ sourceFile,
256
+ rootPath: args.rootPath,
257
+ });
258
+ return {
259
+ id:
260
+ args.id?.trim() ||
261
+ args.info?.sourceId?.trim() ||
262
+ args.info?.selector?.trim() ||
263
+ undefined,
264
+ // Keep the raw Fiber value only in local state. Prompt serialization goes
265
+ // through redactReactSourceAnchor, which omits absolute paths until the
266
+ // connection root has resolved them to a safe project-relative relPath.
267
+ sourceFile,
268
+ ...(relPath ? { relPath } : {}),
269
+ line: provenance.line,
270
+ column: provenance.column,
271
+ component: provenance.component,
272
+ runtimeMultiplicity,
273
+ ...(args.reason?.trim() ? { reason: args.reason.trim() } : {}),
274
+ scope:
275
+ args.scope ?? (runtimeMultiplicity > 1 ? "repeated-render" : "unknown"),
276
+ };
277
+ }
278
+
130
279
  export type PendingLiveNonStyleEdit =
131
280
  | PendingLiveTextEdit
132
281
  | PendingLiveStructureEdit;
@@ -289,6 +438,7 @@ export function formatPendingVisualStylePrompt(args: {
289
438
  screenName: edit.screenName,
290
439
  selector: edit.selector,
291
440
  sourceId: edit.sourceId ?? null,
441
+ sourceAnchor: redactReactSourceAnchor(edit.sourceAnchor),
292
442
  tagName: edit.tagName ?? null,
293
443
  classes: edit.classes,
294
444
  styles: edit.styles,
@@ -297,6 +447,21 @@ export function formatPendingVisualStylePrompt(args: {
297
447
  const hasBreakpointScopedEdits = args.edits.some(
298
448
  (edit) => edit.breakpoint && edit.breakpoint.upperBoundPx !== null,
299
449
  );
450
+ const reactSourceAnchors = [
451
+ ...args.edits.map((edit) => edit.sourceAnchor),
452
+ ...(args.liveEdits ?? []).flatMap((edit) =>
453
+ edit.kind === "structure"
454
+ ? [edit.sourceAnchor, edit.anchorSourceAnchor]
455
+ : [edit.sourceAnchor],
456
+ ),
457
+ ].filter((anchor): anchor is ReactSourceAnchor => Boolean(anchor));
458
+ const hasReactSourceAnchors = reactSourceAnchors.length > 0;
459
+ const hasRepeatedOrSharedReactScope = reactSourceAnchors.some(
460
+ (anchor) =>
461
+ (anchor.runtimeMultiplicity ?? 1) > 1 ||
462
+ anchor.scope === "repeated-render" ||
463
+ anchor.scope === "shared-component-definition",
464
+ );
300
465
  const liveEditPayload = (args.liveEdits ?? []).map((edit) => {
301
466
  if (edit.kind === "text") {
302
467
  return {
@@ -306,12 +471,44 @@ export function formatPendingVisualStylePrompt(args: {
306
471
  screenName: edit.screenName,
307
472
  selector: edit.selector,
308
473
  sourceId: edit.sourceId ?? null,
474
+ sourceAnchor: redactReactSourceAnchor(edit.sourceAnchor),
309
475
  tagName: edit.tagName ?? null,
310
476
  classes: edit.classes,
311
477
  value: edit.value,
312
478
  html: edit.html,
313
479
  };
314
480
  }
481
+ const subjectAnchor = edit.sourceAnchor
482
+ ? { ...edit.sourceAnchor, id: "subject" }
483
+ : undefined;
484
+ const targetAnchor = edit.anchorSourceAnchor
485
+ ? { ...edit.anchorSourceAnchor, id: "target" }
486
+ : undefined;
487
+ const semanticHandoff =
488
+ subjectAnchor && targetAnchor
489
+ ? buildReactSemanticHandoff({
490
+ operation: edit.placement === "inside" ? "reparent" : "move",
491
+ desiredChange: `Move the selected runtime element ${edit.placement} the target runtime element.`,
492
+ sourceAnchors: [subjectAnchor, targetAnchor],
493
+ runtimeRelationship: {
494
+ kind: edit.placement,
495
+ subjectAnchorIds: ["subject"],
496
+ targetAnchorId: "target",
497
+ screenId: edit.screenId,
498
+ description: `${edit.selector} ${edit.placement} ${edit.anchorSelector}`,
499
+ },
500
+ // The packet intentionally starts without a hash: its execution
501
+ // contract requires read-local-file before every write.
502
+ versionHashes: [],
503
+ })
504
+ : {
505
+ ok: false as const,
506
+ rejection: {
507
+ code: "missing-source-provenance" as const,
508
+ reason:
509
+ "Exact subject and target source anchors were not both available for this React structure edit.",
510
+ },
511
+ };
315
512
  return {
316
513
  kind: edit.kind,
317
514
  screenId: edit.screenId,
@@ -319,9 +516,14 @@ export function formatPendingVisualStylePrompt(args: {
319
516
  screenName: edit.screenName,
320
517
  selector: edit.selector,
321
518
  sourceId: edit.sourceId ?? null,
519
+ sourceAnchor: redactReactSourceAnchor(edit.sourceAnchor),
322
520
  anchorSelector: edit.anchorSelector,
323
521
  anchorSourceId: edit.anchorSourceId ?? null,
522
+ anchorSourceAnchor: redactReactSourceAnchor(edit.anchorSourceAnchor),
324
523
  placement: edit.placement,
524
+ ...(semanticHandoff.ok
525
+ ? { semanticHandoff: semanticHandoff.handoff }
526
+ : { semanticHandoffFailure: semanticHandoff.rejection }),
325
527
  };
326
528
  });
327
529
 
@@ -333,6 +535,12 @@ export function formatPendingVisualStylePrompt(args: {
333
535
  : "",
334
536
  "",
335
537
  "Use the Design source tools to make the source match the current live canvas preview. Read each target screen, resolve source ids/selectors through the code-layer projection, then apply the style, text, and structure changes with focused source edits. Preserve layout, behavior, and unrelated styling.",
538
+ hasReactSourceAnchors
539
+ ? "React sourceAnchor fields are source provenance; runtime source ids and selectors are correlation hints only. Verify every project-relative file, line, column, component, and surrounding control flow before editing. Never use a generic AST reparent, group, wrapper, or other structural transform. For semantic structure edits, follow the embedded semanticHandoff packet and use this exact guarded sequence: read-local-file, capture its versionHash, obtain human write consent, write-local-file with expectedVersionHash and requireExpectedVersionHash: true, then keep the preview pending until HMR proves the intended runtime relationship. On a version conflict, re-read and re-plan; never overwrite blindly."
540
+ : "",
541
+ hasRepeatedOrSharedReactScope
542
+ ? "At least one React anchor is repeated at runtime or resolves to a shared component definition. Inspect map/conditional/component call sites and confirm whether the change should affect one instance or every instance before writing source."
543
+ : "",
336
544
  hasBreakpointScopedEdits
337
545
  ? "Edits that carry a `breakpoint` field were made while a narrower breakpoint frame was active: apply them as width-scoped overrides (apply-visual-edit with `activeFrameWidthPx` set to breakpoint.activeWidthPx), NOT as base writes — base values must keep rendering at wider viewports."
338
546
  : "",
@@ -361,6 +569,43 @@ export function resolveOverviewScreenSourceType(
361
569
  );
362
570
  }
363
571
 
572
+ export function shouldUseRuntimeLayerProjection(args: {
573
+ screen:
574
+ | { sourceType?: unknown; bridgeUrl?: string | null }
575
+ | null
576
+ | undefined;
577
+ fallbackSourceType?: DesignSourceType;
578
+ content: string;
579
+ }): boolean {
580
+ if (
581
+ resolveOverviewScreenSourceType(
582
+ args.screen,
583
+ args.fallbackSourceType ?? "inline",
584
+ ) !== "localhost"
585
+ ) {
586
+ return false;
587
+ }
588
+ try {
589
+ const url = new URL(args.content.trim());
590
+ return url.protocol === "http:" || url.protocol === "https:";
591
+ } catch {
592
+ return false;
593
+ }
594
+ }
595
+
596
+ export function shouldPreferRuntimeLayerProjection(args: {
597
+ eligible: boolean;
598
+ runtimeNodeCount: number;
599
+ sourceNodeCount: number;
600
+ }): boolean {
601
+ // A hydrated localhost tree is the visible app's ground truth even when SSR
602
+ // happened to emit the same number of nodes (or more wrappers). Keep the
603
+ // source projection separately for writes; never use counts to decide which
604
+ // tree represents the live Layers panel.
605
+ void args.sourceNodeCount;
606
+ return args.eligible && args.runtimeNodeCount > 0;
607
+ }
608
+
364
609
  export function shouldShowPendingVisualStyleApply(args: {
365
610
  edits: readonly PendingVisualStyleEdit[];
366
611
  liveEdits?: readonly PendingLiveNonStyleEdit[];