@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
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Client-side Figma clipboard helpers: decode the `figmeta` marker Figma
3
+ * writes into a copy's clipboard HTML, and decide which action a paste
4
+ * should route through.
5
+ *
6
+ * Figma wraps a small base64 JSON payload (`{"fileKey","pasteID","dataType"}`)
7
+ * between literal `(figmeta)`/`(/figmeta)` markers — the same markers
8
+ * `hasFigmaClipboardPayload` in `design-import.ts` already scans for to detect
9
+ * a Figma paste at all. Some Figma clients may instead put that same base64
10
+ * blob directly in the `data-metadata` attribute with no wrapper markers, so
11
+ * `extractFigmeta` tries both, defensively, and never throws — any malformed
12
+ * input just means "not a decodable Figma paste".
13
+ *
14
+ * Important caveat carried through to the server: `figmeta` has no node ids
15
+ * (only a `fileKey`), and `pasteID` is an ephemeral, server-side identifier
16
+ * that isn't resolvable to a node through the public REST API. So a plain
17
+ * clipboard paste can only ever get an exact node import on a best-effort,
18
+ * heuristic match (see `server/lib/figma-clipboard-match.ts`) — a copied
19
+ * frame LINK (which carries a real `node-id`) is the only guaranteed-exact
20
+ * path (`import-figma-frame`).
21
+ */
22
+
23
+ export interface FigmetaPayload {
24
+ fileKey: string;
25
+ pasteID?: number;
26
+ dataType?: string;
27
+ }
28
+
29
+ const FIGMETA_MARKER_RE = /\(figmeta\)([^(]*?)\(\/figmeta\)/i;
30
+ const DATA_METADATA_ATTR_RE = /\bdata-metadata\s*=\s*(["'])([\s\S]*?)\1/i;
31
+
32
+ function decodeBase64Json(raw: string): unknown {
33
+ const trimmed = raw.trim();
34
+ if (!trimmed) return null;
35
+ try {
36
+ const decoded =
37
+ typeof atob === "function"
38
+ ? atob(trimmed)
39
+ : Buffer.from(trimmed, "base64").toString("utf8");
40
+ return JSON.parse(decoded);
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ function isFigmetaPayload(value: unknown): value is FigmetaPayload {
47
+ if (!value || typeof value !== "object") return false;
48
+ const fileKey = (value as { fileKey?: unknown }).fileKey;
49
+ return typeof fileKey === "string" && fileKey.trim().length > 0;
50
+ }
51
+
52
+ function normalizeFigmetaPayload(value: FigmetaPayload): FigmetaPayload {
53
+ const payload: FigmetaPayload = { fileKey: value.fileKey.trim() };
54
+ if (typeof value.pasteID === "number") payload.pasteID = value.pasteID;
55
+ if (typeof value.dataType === "string") payload.dataType = value.dataType;
56
+ return payload;
57
+ }
58
+
59
+ /**
60
+ * Extracts and decodes the `figmeta` payload from a clipboard's raw HTML (or
61
+ * plain text), if present. Returns `null` for anything that isn't a
62
+ * decodable Figma `figmeta` marker — this never throws, so callers can run it
63
+ * unconditionally on every paste.
64
+ */
65
+ export function extractFigmeta(
66
+ html: string | null | undefined,
67
+ ): FigmetaPayload | null {
68
+ if (!html) return null;
69
+
70
+ const markerMatch = html.match(FIGMETA_MARKER_RE);
71
+ if (markerMatch?.[1]) {
72
+ const parsed = decodeBase64Json(markerMatch[1]);
73
+ if (isFigmetaPayload(parsed)) return normalizeFigmetaPayload(parsed);
74
+ }
75
+
76
+ // Defensive fallback: some clients may emit data-metadata as a bare base64
77
+ // blob with no (figmeta)...(/figmeta) wrapper markers.
78
+ const attrMatch = html.match(DATA_METADATA_ATTR_RE);
79
+ if (attrMatch?.[2]) {
80
+ const parsed = decodeBase64Json(attrMatch[2]);
81
+ if (isFigmetaPayload(parsed)) return normalizeFigmetaPayload(parsed);
82
+ }
83
+
84
+ return null;
85
+ }
86
+
87
+ export type FigmaApiKeyStatus = "configured" | "missing" | "unknown";
88
+ export type FigmaPasteStrategy = "rest" | "html-fallback" | "not-figma";
89
+
90
+ /**
91
+ * Pure decision matrix for what a paste should attempt, given whether it
92
+ * decoded a `figmeta` payload and (if known) whether the Figma access token
93
+ * is configured:
94
+ *
95
+ * - No `figmeta` -> not a Figma paste at all (`"not-figma"`).
96
+ * - `figmeta` present but the key is known-missing -> skip the REST attempt
97
+ * entirely and go straight to the legacy HTML path (`"html-fallback"`).
98
+ * - `figmeta` present and the key is configured, or its status isn't known
99
+ * client-side -> attempt the REST import (`"rest"`). The server is the
100
+ * authority on whether the key actually works; passing `"unknown"` here
101
+ * just means "try REST, let the server fall back for real if it can't."
102
+ */
103
+ export function decideFigmaPasteStrategy(
104
+ figmeta: FigmetaPayload | null,
105
+ apiKeyStatus: FigmaApiKeyStatus = "unknown",
106
+ ): FigmaPasteStrategy {
107
+ if (!figmeta) return "not-figma";
108
+ if (apiKeyStatus === "missing") return "html-fallback";
109
+ return "rest";
110
+ }
111
+
112
+ export type FigmaPasteImportCall =
113
+ | {
114
+ action: "import-figma-clipboard";
115
+ payload: {
116
+ figmetaFileKey: string;
117
+ clipboardHtml: string;
118
+ originalName?: string;
119
+ };
120
+ }
121
+ | {
122
+ action: "import-design-source";
123
+ payload: {
124
+ sourceType: "figma-paste-html";
125
+ content: string;
126
+ originalName?: string;
127
+ };
128
+ };
129
+
130
+ /**
131
+ * Resolves the actual action name + payload a paste should call, given the
132
+ * raw clipboard HTML. Used by both the canvas-level paste listener and the
133
+ * Import panel's Figma-paste target so the two call sites stay in lockstep.
134
+ */
135
+ export function resolveFigmaPasteImportCall(
136
+ content: string,
137
+ originalName = "figma-paste.html",
138
+ ): FigmaPasteImportCall {
139
+ const figmeta = extractFigmeta(content);
140
+ const strategy = decideFigmaPasteStrategy(figmeta, "unknown");
141
+
142
+ if (strategy === "not-figma") {
143
+ return {
144
+ action: "import-design-source",
145
+ payload: { sourceType: "figma-paste-html", content, originalName },
146
+ };
147
+ }
148
+
149
+ return {
150
+ action: "import-figma-clipboard",
151
+ payload: {
152
+ figmetaFileKey: figmeta!.fileKey,
153
+ clipboardHtml: content,
154
+ originalName,
155
+ },
156
+ };
157
+ }
@@ -0,0 +1,221 @@
1
+ import { callAction } from "@agent-native/core/client";
2
+
3
+ export type FigmaSvgCopyErrorCode =
4
+ | "unsupported"
5
+ | "blocked"
6
+ | "write-failed"
7
+ | "render-failed";
8
+
9
+ export class FigmaSvgCopyError extends Error {
10
+ readonly code: FigmaSvgCopyErrorCode;
11
+
12
+ constructor(code: FigmaSvgCopyErrorCode, cause?: unknown) {
13
+ super(`Figma SVG copy ${code}`);
14
+ this.name = "FigmaSvgCopyError";
15
+ this.code = code;
16
+ if (cause !== undefined) {
17
+ Object.defineProperty(this, "cause", {
18
+ configurable: true,
19
+ value: cause,
20
+ });
21
+ }
22
+ }
23
+ }
24
+
25
+ type ClipboardWriter = Pick<Clipboard, "write"> & {
26
+ writeText?: Clipboard["writeText"];
27
+ };
28
+
29
+ type ClipboardItemConstructor = {
30
+ new (items: Record<string, Blob | Promise<Blob>>): ClipboardItem;
31
+ supports?: (type: string) => boolean;
32
+ };
33
+
34
+ export interface FigmaSvgExportActionResult {
35
+ ok: boolean;
36
+ reason?: string;
37
+ svg?: string;
38
+ filename?: string;
39
+ report?: unknown;
40
+ }
41
+
42
+ export interface FigmaSvgExportParams {
43
+ designId?: string;
44
+ fileId?: string;
45
+ filename?: string;
46
+ nodeId?: string;
47
+ embedImages?: boolean;
48
+ }
49
+
50
+ export interface FigmaSvgCopyEnvironment {
51
+ clipboard?: ClipboardWriter | null;
52
+ ClipboardItem?: ClipboardItemConstructor | null;
53
+ /** Injectable override for tests — defaults to the real `callAction`. */
54
+ callExportAction?: (
55
+ params: FigmaSvgExportParams,
56
+ ) => Promise<FigmaSvgExportActionResult>;
57
+ }
58
+
59
+ function defaultFigmaSvgCopyEnvironment(): FigmaSvgCopyEnvironment {
60
+ return {
61
+ clipboard:
62
+ typeof navigator === "undefined" ? null : (navigator.clipboard ?? null),
63
+ ClipboardItem:
64
+ typeof globalThis.ClipboardItem === "undefined"
65
+ ? null
66
+ : globalThis.ClipboardItem,
67
+ };
68
+ }
69
+
70
+ function defaultCallExportAction(
71
+ params: FigmaSvgExportParams,
72
+ ): Promise<FigmaSvgExportActionResult> {
73
+ // Same cast-to-loose-signature pattern as design-save-outbox.ts's
74
+ // `invokeAction` default: the action registry's generated `ActionName`
75
+ // union doesn't need to be threaded through this small client module.
76
+ return (
77
+ callAction as (
78
+ name: string,
79
+ params: Record<string, unknown>,
80
+ ) => Promise<FigmaSvgExportActionResult>
81
+ )("export-design-as-figma-svg", params as unknown as Record<string, unknown>);
82
+ }
83
+
84
+ export function canCopyFigmaSvgToClipboard(
85
+ environment: FigmaSvgCopyEnvironment = defaultFigmaSvgCopyEnvironment(),
86
+ ): boolean {
87
+ // `text/plain` (the proven Figma-paste MIME — see the export-handoff skill's
88
+ // "Export to Figma (SVG)" section) only needs a plain `clipboard.write` or
89
+ // `writeText`; ClipboardItem is optional (only gates the extra
90
+ // `image/svg+xml` representation).
91
+ return Boolean(
92
+ (environment.clipboard?.write && environment.ClipboardItem) ||
93
+ environment.clipboard?.writeText,
94
+ );
95
+ }
96
+
97
+ function supportsClipboardType(
98
+ ClipboardItemCtor: ClipboardItemConstructor,
99
+ type: string,
100
+ ): boolean {
101
+ try {
102
+ return (
103
+ typeof ClipboardItemCtor.supports !== "function" ||
104
+ ClipboardItemCtor.supports(type)
105
+ );
106
+ } catch {
107
+ return false;
108
+ }
109
+ }
110
+
111
+ function classifyClipboardWriteError(error: unknown): FigmaSvgCopyErrorCode {
112
+ const name =
113
+ error && typeof error === "object" && "name" in error
114
+ ? String(error.name)
115
+ : "";
116
+ if (name === "NotAllowedError" || name === "SecurityError") return "blocked";
117
+ if (name === "NotSupportedError" || error instanceof TypeError)
118
+ return "unsupported";
119
+ return "write-failed";
120
+ }
121
+
122
+ export interface FigmaSvgCopyResult {
123
+ filename: string;
124
+ report: unknown;
125
+ }
126
+
127
+ /**
128
+ * Exports a design screen (or a selected element's subtree via `nodeId`) as
129
+ * a genuinely vector SVG through the `export-design-as-figma-svg` action,
130
+ * then writes it to the system clipboard as BOTH:
131
+ *
132
+ * - `text/plain` — the raw SVG markup. This is the MIME Figma's own paste
133
+ * handler reads for "paste as vector shapes"; a `image/svg+xml`-only
134
+ * clipboard write is NOT enough on its own for a reliable Figma paste.
135
+ * - `image/svg+xml` — the same markup as a typed image representation,
136
+ * for any other paste target that specifically requests SVG images.
137
+ *
138
+ * Call this from a user-gesture handler (e.g. a context-menu "Copy as SVG"
139
+ * item) — `clipboard.write` requires transient activation in most browsers,
140
+ * the same reason `copyPngPromiseToClipboard` in `png-clipboard.ts` is
141
+ * gesture-scoped.
142
+ */
143
+ export async function copyDesignAsFigmaSvg(
144
+ params: FigmaSvgExportParams,
145
+ environment: FigmaSvgCopyEnvironment = defaultFigmaSvgCopyEnvironment(),
146
+ ): Promise<FigmaSvgCopyResult> {
147
+ if (!canCopyFigmaSvgToClipboard(environment)) {
148
+ throw new FigmaSvgCopyError("unsupported");
149
+ }
150
+
151
+ const callExportAction =
152
+ environment.callExportAction ?? defaultCallExportAction;
153
+ const clipboard = environment.clipboard;
154
+ const ClipboardItemCtor = environment.ClipboardItem;
155
+
156
+ let renderError: unknown;
157
+ const exportPromise = callExportAction(params)
158
+ .then((result) => {
159
+ if (!result.ok || !result.svg) {
160
+ throw new FigmaSvgCopyError(
161
+ "render-failed",
162
+ new Error(result.reason ?? "Figma SVG export failed"),
163
+ );
164
+ }
165
+ return result as FigmaSvgExportActionResult & { svg: string };
166
+ })
167
+ .catch((error: unknown) => {
168
+ renderError =
169
+ error instanceof FigmaSvgCopyError
170
+ ? error
171
+ : new FigmaSvgCopyError("render-failed", error);
172
+ throw renderError;
173
+ });
174
+ // ClipboardItem owns the promises below in real browsers. Keep a separate
175
+ // observer so test doubles or an early clipboard rejection cannot surface an
176
+ // unhandled action/render rejection.
177
+ void exportPromise.catch(() => undefined);
178
+
179
+ try {
180
+ if (clipboard?.write && ClipboardItemCtor) {
181
+ // Call clipboard.write while the initiating click/key event still owns
182
+ // transient activation. ClipboardItem deliberately receives pending
183
+ // Blob promises, matching the proven PNG clipboard path; awaiting the
184
+ // server render first makes slow exports fail in Safari and hardened
185
+ // Chromium even though the user invoked the command correctly.
186
+ const textBlobPromise = exportPromise.then(
187
+ (result) => new Blob([result.svg], { type: "text/plain" }),
188
+ );
189
+ void textBlobPromise.catch(() => undefined);
190
+ const items: Record<string, Blob | Promise<Blob>> = {
191
+ "text/plain": textBlobPromise,
192
+ };
193
+ if (supportsClipboardType(ClipboardItemCtor, "image/svg+xml")) {
194
+ const svgBlobPromise = exportPromise.then(
195
+ (result) => new Blob([result.svg], { type: "image/svg+xml" }),
196
+ );
197
+ void svgBlobPromise.catch(() => undefined);
198
+ items["image/svg+xml"] = svgBlobPromise;
199
+ }
200
+ await clipboard.write([new ClipboardItemCtor(items)]);
201
+ } else if (clipboard?.writeText) {
202
+ // No ClipboardItem constructor available — still deliver the SVG
203
+ // markup as text/plain, which is the proven Figma-paste path anyway.
204
+ const result = await exportPromise;
205
+ await clipboard.writeText(result.svg);
206
+ } else {
207
+ throw new FigmaSvgCopyError("unsupported");
208
+ }
209
+ } catch (error) {
210
+ if (error instanceof FigmaSvgCopyError) throw error;
211
+ if (renderError !== undefined) throw renderError;
212
+ throw new FigmaSvgCopyError(classifyClipboardWriteError(error), error);
213
+ }
214
+
215
+ const result = await exportPromise;
216
+
217
+ return {
218
+ filename: result.filename ?? "design-figma.svg",
219
+ report: result.report,
220
+ };
221
+ }
@@ -306,11 +306,41 @@ export function bridgeSourceIdForCodeLayerNode(node: CodeLayerNode): string {
306
306
  );
307
307
  }
308
308
 
309
+ function positiveIntegerDataAttribute(
310
+ value: string | undefined,
311
+ ): number | undefined {
312
+ const trimmed = value?.trim();
313
+ if (!trimmed || !/^\d+$/.test(trimmed)) return undefined;
314
+ const parsed = Number(trimmed);
315
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
316
+ }
317
+
318
+ function provenanceForCodeLayerNode(
319
+ node: CodeLayerNode,
320
+ ): ElementInfo["provenance"] {
321
+ const sourceFile = node.dataAttributes["data-source-file"]?.trim();
322
+ const line = positiveIntegerDataAttribute(
323
+ node.dataAttributes["data-source-line"],
324
+ );
325
+ const column = positiveIntegerDataAttribute(
326
+ node.dataAttributes["data-source-column"],
327
+ );
328
+ const component = node.dataAttributes["data-component-name"]?.trim();
329
+ if (!sourceFile && !line && !column && !component) return undefined;
330
+ return {
331
+ ...(sourceFile ? { sourceFile } : {}),
332
+ ...(line ? { line } : {}),
333
+ ...(column ? { column } : {}),
334
+ ...(component ? { component } : {}),
335
+ };
336
+ }
337
+
309
338
  export function elementInfoFromCodeLayerNode(node: CodeLayerNode): ElementInfo {
310
339
  return {
311
340
  tagName: node.tag,
312
341
  id: typeof node.attributes.id === "string" ? node.attributes.id : undefined,
313
342
  sourceId: bridgeSourceIdForCodeLayerNode(node),
343
+ provenance: provenanceForCodeLayerNode(node),
314
344
  selector: preferredCodeLayerSelector(node),
315
345
  classes: node.classes,
316
346
  computedStyles: Object.fromEntries(
@@ -347,15 +377,83 @@ export function cssStyleAliases(
347
377
  return result;
348
378
  }
349
379
 
380
+ // BUG-UNDO-RESIZE-GEOMETRY: width/height are the two computedStyles keys the
381
+ // Layout panel's W/H fields actually read (edit-panel/element-classification.ts's
382
+ // cssElementSize parses element.computedStyles.width/height first, falling
383
+ // back to boundingRect only when that's missing/unparseable). Every other
384
+ // computedStyles property is fine to carry over additively when the fresh
385
+ // (reverted/replayed) source doesn't mention it — most style properties come
386
+ // from a CSS class the string-parse can't see, so keeping the previously
387
+ // known value is the right default. Width/height are different: an undo or
388
+ // redo of a resize commit is EXACTLY a change to (or removal of) an inline
389
+ // width/height, so a stale carried-over value here isn't just incomplete —
390
+ // it's actively wrong (it's the pre-undo/pre-redo box size).
391
+ const GEOMETRY_STYLE_PROPERTIES = ["width", "height"] as const;
392
+
350
393
  export function refreshedComputedStyles(
351
394
  info: ElementInfo,
352
395
  sourceStyles: Record<string, string>,
353
396
  sourceClasses: readonly string[],
354
397
  ): Record<string, string> {
355
398
  const sourceWithAliases = cssStyleAliases(sourceStyles);
356
- return sourceClasses.length > 0
357
- ? { ...info.computedStyles, ...sourceWithAliases }
358
- : sourceWithAliases;
399
+ const merged: Record<string, string> =
400
+ sourceClasses.length > 0
401
+ ? { ...info.computedStyles, ...sourceWithAliases }
402
+ : { ...sourceWithAliases };
403
+ GEOMETRY_STYLE_PROPERTIES.forEach((property) => {
404
+ if (!(property in sourceWithAliases)) delete merged[property];
405
+ });
406
+ return merged;
407
+ }
408
+
409
+ /**
410
+ * Companion to `refreshedComputedStyles` for the same undo/redo/remote-sync
411
+ * resync path — `ElementInfo.boundingRect` is otherwise left completely
412
+ * untouched by `refreshElementInfoFromContent` (both its node-match branch,
413
+ * via `canonicalElementInfoForCodeLayerNode`'s `{...info}` spread, and its
414
+ * DOM-parse fallback, via its own `{...info}` spread), so it keeps showing
415
+ * whatever rect was live-measured before the content change. `cssElementSize`
416
+ * falls back to `boundingRect.width`/`height` whenever computedStyles has no
417
+ * parseable value for that axis, so a permanently-stale boundingRect can
418
+ * still surface the pre-undo/redo size even after `refreshedComputedStyles`
419
+ * is fixed. Reuses the SAME freshly-resolved computedStyles (already merged
420
+ * above) so both fields agree, instead of re-deriving from a different
421
+ * source.
422
+ *
423
+ * Exported for unit testing.
424
+ */
425
+ export function refreshedBoundingRectSize(
426
+ info: ElementInfo,
427
+ computedStyles: Record<string, string>,
428
+ ): ElementInfo["boundingRect"] {
429
+ const parsedWidth = parseFloat(computedStyles.width ?? "");
430
+ const parsedHeight = parseFloat(computedStyles.height ?? "");
431
+ return {
432
+ ...info.boundingRect,
433
+ width:
434
+ Number.isFinite(parsedWidth) && parsedWidth >= 0
435
+ ? parsedWidth
436
+ : info.boundingRect.width,
437
+ height:
438
+ Number.isFinite(parsedHeight) && parsedHeight >= 0
439
+ ? parsedHeight
440
+ : info.boundingRect.height,
441
+ };
442
+ }
443
+
444
+ function codeLayerNodeMatchesSourceId(
445
+ node: CodeLayerNode,
446
+ sourceId: string,
447
+ ): boolean {
448
+ return (
449
+ node.id === sourceId ||
450
+ node.dataAttributes["data-agent-native-node-id"] === sourceId ||
451
+ node.dataAttributes["data-code-layer-id"] === sourceId ||
452
+ node.dataAttributes["data-layer-id"] === sourceId ||
453
+ node.dataAttributes["data-builder-id"] === sourceId ||
454
+ node.dataAttributes["data-loc"] === sourceId ||
455
+ node.attributes.id === sourceId
456
+ );
359
457
  }
360
458
 
361
459
  export function codeLayerNodeMatchesBridgeTarget(
@@ -363,19 +461,7 @@ export function codeLayerNodeMatchesBridgeTarget(
363
461
  selector?: string,
364
462
  sourceId?: string,
365
463
  ): boolean {
366
- if (sourceId) {
367
- if (node.id === sourceId) return true;
368
- if (
369
- node.dataAttributes["data-agent-native-node-id"] === sourceId ||
370
- node.dataAttributes["data-code-layer-id"] === sourceId ||
371
- node.dataAttributes["data-layer-id"] === sourceId ||
372
- node.dataAttributes["data-builder-id"] === sourceId ||
373
- node.dataAttributes["data-loc"] === sourceId ||
374
- node.attributes.id === sourceId
375
- ) {
376
- return true;
377
- }
378
- }
464
+ if (sourceId && codeLayerNodeMatchesSourceId(node, sourceId)) return true;
379
465
  return codeLayerSelectorMatches(node, selector);
380
466
  }
381
467
 
@@ -384,11 +470,34 @@ export function resolveCodeLayerNodeFromBridge(
384
470
  selector?: string,
385
471
  sourceId?: string,
386
472
  ): CodeLayerNode | null {
387
- return (
388
- projection.nodes.find((node) =>
389
- codeLayerNodeMatchesBridgeTarget(node, selector, sourceId),
390
- ) ?? null
473
+ // Id-based match first, across the WHOLE projection (not just up to
474
+ // whichever node the old combined-predicate `.find()` reached first) — a
475
+ // sourceId identifies one node by its own stable id/data-attribute, which
476
+ // is unique in a well-formed projection, so this branch alone can never be
477
+ // ambiguous. Checking it before the selector fallback also fixes a subtler
478
+ // pre-existing bug: the old single-pass `.find()` could match an EARLIER
479
+ // node purely by selector before ever reaching the correct sourceId match
480
+ // later in iteration order.
481
+ if (sourceId) {
482
+ const idMatch = projection.nodes.find((node) =>
483
+ codeLayerNodeMatchesSourceId(node, sourceId),
484
+ );
485
+ if (idMatch) return idMatch;
486
+ }
487
+ // No sourceId (or no node carries it yet, e.g. a bridge target minted a
488
+ // fresh pending id the projection hasn't picked up) — fall back to the
489
+ // bridge's structural selector. Unlike an id, a generic short selector
490
+ // (e.g. a 2-segment "div > p" suffix path) can legitimately match several
491
+ // repeated list/card/row instances. The server-side resolver for the same
492
+ // problem (code-layer.ts's resolveTarget) explicitly refuses to resolve a
493
+ // selector that matches more than one node rather than silently picking
494
+ // the first DOM-order match — mirror that discipline here so a duplicate/
495
+ // move/style edit can never silently land on the wrong sibling instance.
496
+ if (!selector) return null;
497
+ const selectorMatches = projection.nodes.filter((node) =>
498
+ codeLayerSelectorMatches(node, selector),
391
499
  );
500
+ return selectorMatches.length === 1 ? selectorMatches[0]! : null;
392
501
  }
393
502
 
394
503
  export function collapsedElementText(value: string | null | undefined): string {
@@ -606,13 +715,15 @@ export function refreshElementInfoFromContent(
606
715
  );
607
716
  if (node) {
608
717
  const sourceInfo = elementInfoFromCodeLayerNode(node);
718
+ const computedStyles = refreshedComputedStyles(
719
+ info,
720
+ sourceInfo.computedStyles,
721
+ sourceInfo.classes,
722
+ );
609
723
  return {
610
724
  ...canonicalElementInfoForCodeLayerNode(info, node),
611
- computedStyles: refreshedComputedStyles(
612
- info,
613
- sourceInfo.computedStyles,
614
- sourceInfo.classes,
615
- ),
725
+ computedStyles,
726
+ boundingRect: refreshedBoundingRectSize(info, computedStyles),
616
727
  textContent: sourceInfo.textContent,
617
728
  childElementCount: sourceInfo.childElementCount,
618
729
  isFlexChild: sourceInfo.isFlexChild,
@@ -626,14 +737,16 @@ export function refreshElementInfoFromContent(
626
737
  const element = queryUniqueSelector(doc, info.selector);
627
738
  if (!element) return null;
628
739
  const classes = Array.from(element.classList);
740
+ const computedStyles = refreshedComputedStyles(
741
+ info,
742
+ parseInlineStyleAttribute(element.getAttribute("style")),
743
+ classes,
744
+ );
629
745
  return {
630
746
  ...info,
631
747
  classes,
632
- computedStyles: refreshedComputedStyles(
633
- info,
634
- parseInlineStyleAttribute(element.getAttribute("style")),
635
- classes,
636
- ),
748
+ computedStyles,
749
+ boundingRect: refreshedBoundingRectSize(info, computedStyles),
637
750
  textContent: element.textContent?.slice(0, 200) ?? info.textContent,
638
751
  childElementCount: element.children.length,
639
752
  };
@@ -41,3 +41,18 @@ export function resolveScreenCollabSyncTarget({
41
41
  overviewDocConnected && overviewPresenceFileId === fileId;
42
42
  return { writeLiveDoc, syncCollab: !writeLiveDoc };
43
43
  }
44
+
45
+ /** A local transaction already updated the preview optimistically, and a
46
+ * same-content remote transaction is only an acknowledgement echo. Only a
47
+ * genuinely different remote snapshot should touch the live document. */
48
+ export function shouldApplyRemotePreviewContent({
49
+ isLocalEdit,
50
+ previousContent,
51
+ nextContent,
52
+ }: {
53
+ isLocalEdit: boolean;
54
+ previousContent: string | null;
55
+ nextContent: string;
56
+ }): boolean {
57
+ return !isLocalEdit && nextContent !== previousContent;
58
+ }
@@ -18,3 +18,36 @@ export function createEditorSaveOperationSource(
18
18
 
19
19
  /** Yjs origin tracked by the local undo manager. */
20
20
  export const LOCAL_EDIT_ORIGIN = `${TAB_ID}:local`;
21
+
22
+ /**
23
+ * TIE-BREAK: decides whether polled DB content should be adopted into the
24
+ * live editor during the file-content reconcile effect (DesignEditor.tsx).
25
+ * Callers only reach this decision once BOTH of the effect's own
26
+ * "already reflecting this exact content" early-returns have already ruled
27
+ * out `dbContent` matching what's currently rendered — so every call here
28
+ * represents a genuine content difference.
29
+ *
30
+ * A strict `dbUpdatedAt > applied` used to be the only "is this newer"
31
+ * check, which silently DROPPED a real external write whenever it landed in
32
+ * the same millisecond as the previously-applied one (timestamp
33
+ * resolution) — the tie was never treated as "newer," so the second write's
34
+ * content just never rendered. That's only safe to fix by treating a tie as
35
+ * adoptable while `agentActive` is false: when the agent IS active, a tied
36
+ * timestamp with different content is more likely a live self-echo race
37
+ * (the agent's own Yjs-tracked edit still mid-flight), which the caller's
38
+ * `staleAgentEchoPossible` debounced recovery timer already handles
39
+ * separately and more carefully — forcing immediate adoption for that case
40
+ * too would skip the debounce and reintroduce the exact race it exists to
41
+ * prevent.
42
+ */
43
+ export function shouldAdoptExternalReconcileContent(args: {
44
+ appliedUpdatedAt: string | null | undefined;
45
+ dbUpdatedAt: string | null | undefined;
46
+ agentActive: boolean;
47
+ }): boolean {
48
+ const { appliedUpdatedAt, dbUpdatedAt, agentActive } = args;
49
+ if (!appliedUpdatedAt) return true;
50
+ if (!dbUpdatedAt) return false;
51
+ if (dbUpdatedAt > appliedUpdatedAt) return true;
52
+ return dbUpdatedAt === appliedUpdatedAt && !agentActive;
53
+ }