@agent-native/core 0.90.1 → 0.90.2

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 (295) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +14 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/AgentPanel.tsx +1 -1
  5. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +11 -6
  6. package/corpus/core/src/client/use-action.ts +39 -2
  7. package/corpus/core/src/client/use-db-sync.ts +44 -18
  8. package/corpus/core/src/db/create-get-db.ts +32 -2
  9. package/corpus/core/src/server/framework-request-handler.ts +52 -4
  10. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +5 -0
  11. package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +34 -6
  12. package/corpus/templates/assets/AGENTS.md +23 -4
  13. package/corpus/templates/assets/actions/_generation-preset-settings.ts +48 -0
  14. package/corpus/templates/assets/actions/_helpers.ts +8 -1
  15. package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +64 -0
  16. package/corpus/templates/assets/actions/create-generation-preset.ts +11 -3
  17. package/corpus/templates/assets/actions/generate-image-batch.ts +7 -2
  18. package/corpus/templates/assets/actions/generate-image.ts +342 -25
  19. package/corpus/templates/assets/actions/get-library.ts +4 -3
  20. package/corpus/templates/assets/actions/navigate.ts +2 -1
  21. package/corpus/templates/assets/actions/refresh-generation-run.ts +5 -1
  22. package/corpus/templates/assets/actions/update-asset.ts +1 -0
  23. package/corpus/templates/assets/actions/update-generation-preset.ts +8 -1
  24. package/corpus/templates/assets/actions/view-screen.ts +8 -1
  25. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +9 -1
  26. package/corpus/templates/assets/app/hooks/use-image-model-menu.ts +1 -0
  27. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +11 -0
  28. package/corpus/templates/assets/app/i18n/zh-TW.ts +64 -0
  29. package/corpus/templates/assets/app/i18n-data.ts +650 -0
  30. package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -1
  31. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +44 -33
  32. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1474 -0
  33. package/corpus/templates/assets/app/routes/library.tsx +43 -1
  34. package/corpus/templates/assets/server/lib/generation.ts +366 -44
  35. package/corpus/templates/assets/server/lib/image-processing.ts +252 -0
  36. package/corpus/templates/assets/server/lib/preset-skeleton.ts +137 -0
  37. package/corpus/templates/assets/shared/api.ts +47 -0
  38. package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -2
  39. package/corpus/templates/clips/app/components/library/sort-menu.tsx +1 -1
  40. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +114 -33
  41. package/corpus/templates/clips/app/routes/_app.library._index.tsx +27 -1
  42. package/corpus/templates/clips/app/routes/record.tsx +157 -68
  43. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +5 -5
  44. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +20 -3
  45. package/corpus/templates/clips/shared/finalize-recovery.ts +23 -3
  46. package/corpus/templates/content/AGENTS.md +52 -52
  47. package/corpus/templates/content/actions/_builder-cms-read-client.ts +188 -8
  48. package/corpus/templates/content/actions/_builder-docs-client.ts +41 -3
  49. package/corpus/templates/content/actions/_database-source-utils.ts +998 -160
  50. package/corpus/templates/content/actions/_database-utils.ts +44 -4
  51. package/corpus/templates/content/actions/_notion-action-utils.ts +6 -2
  52. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -1
  53. package/corpus/templates/content/actions/attach-content-database-source.ts +85 -12
  54. package/corpus/templates/content/actions/change-content-database-source-role.ts +9 -1
  55. package/corpus/templates/content/actions/delete-content-database.ts +1 -0
  56. package/corpus/templates/content/actions/get-document.ts +11 -2
  57. package/corpus/templates/content/actions/list-content-databases.ts +57 -9
  58. package/corpus/templates/content/actions/prepare-builder-source-review.ts +23 -6
  59. package/corpus/templates/content/actions/refresh-content-database-source.ts +26 -3
  60. package/corpus/templates/content/actions/restore-content-database.ts +1 -0
  61. package/corpus/templates/content/actions/update-document.ts +43 -0
  62. package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +21 -0
  63. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +0 -1
  64. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +70 -28
  65. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +69 -17
  66. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +4 -4
  67. package/corpus/templates/content/app/components/editor/body-hydration.ts +112 -0
  68. package/corpus/templates/content/app/components/editor/builder-body-hydration-pump.ts +52 -0
  69. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +9076 -2876
  70. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +41 -1
  71. package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +36 -11
  72. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +21 -1
  73. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +11 -1
  74. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +11 -8
  75. package/corpus/templates/content/app/global.css +41 -0
  76. package/corpus/templates/content/app/hooks/use-content-database.ts +346 -42
  77. package/corpus/templates/content/app/hooks/use-db-sync.ts +9 -0
  78. package/corpus/templates/content/app/hooks/use-documents.ts +47 -0
  79. package/corpus/templates/content/app/hooks/use-notion.ts +12 -10
  80. package/corpus/templates/content/app/i18n/zh-TW.ts +35 -0
  81. package/corpus/templates/content/app/i18n-data.ts +355 -0
  82. package/corpus/templates/content/app/root.tsx +22 -0
  83. package/corpus/templates/content/changelog/2026-07-01-builder-database-sources-keep-loading-rows-in-the-background.md +6 -0
  84. package/corpus/templates/content/changelog/2026-07-01-builder-source-attach-starts-with-usable-rows.md +6 -0
  85. package/corpus/templates/content/changelog/2026-07-01-builder-source-attachment-now-stays-connected-after-large.md +6 -0
  86. package/corpus/templates/content/changelog/2026-07-01-builder-source-refreshes-now-show-progress-for-large-docs-blog.md +6 -0
  87. package/corpus/templates/content/changelog/2026-07-01-database-pages-now-delete-to-trash-without-route-errors.md +6 -0
  88. package/corpus/templates/content/changelog/2026-07-02-builder-source-refreshes-recover-from-stalled-background-row.md +6 -0
  89. package/corpus/templates/content/changelog/2026-07-02-builder-sourced-rows-open-with-sync-protection.md +6 -0
  90. package/corpus/templates/content/changelog/2026-07-02-database-rows-and-pages-open-faster-from-recently-loaded-dat.md +6 -0
  91. package/corpus/templates/content/changelog/2026-07-02-preview-panels-close-reliably-narrow-sidebar-row-actions-sta.md +6 -0
  92. package/corpus/templates/content/changelog/2026-07-06-builder-sync-progress-no-longer-shows-continue.md +6 -0
  93. package/corpus/templates/content/shared/api.ts +13 -0
  94. package/corpus/templates/content/shared/builder-mdx.ts +16 -3
  95. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +23 -13
  96. package/corpus/templates/design/.agents/skills/responsive-breakpoints/SKILL.md +98 -0
  97. package/corpus/templates/design/.agents/skills/shader-fills/SKILL.md +162 -0
  98. package/corpus/templates/design/AGENTS.md +9 -2
  99. package/corpus/templates/design/actions/add-breakpoint.ts +6 -3
  100. package/corpus/templates/design/actions/apply-a11y-fix.ts +57 -37
  101. package/corpus/templates/design/actions/apply-component-prop-edit.ts +41 -20
  102. package/corpus/templates/design/actions/apply-motion-edit.ts +251 -38
  103. package/corpus/templates/design/actions/apply-shader-fill.ts +90 -66
  104. package/corpus/templates/design/actions/apply-visual-edit.ts +282 -55
  105. package/corpus/templates/design/actions/create-component.ts +29 -60
  106. package/corpus/templates/design/actions/create-file.ts +9 -2
  107. package/corpus/templates/design/actions/duplicate-design.ts +31 -2
  108. package/corpus/templates/design/actions/edit-design.ts +36 -38
  109. package/corpus/templates/design/actions/generate-design-title.ts +87 -0
  110. package/corpus/templates/design/actions/generate-design.ts +53 -18
  111. package/corpus/templates/design/actions/generate-screens.ts +108 -4
  112. package/corpus/templates/design/actions/get-motion-timeline.ts +35 -7
  113. package/corpus/templates/design/actions/insert-asset.ts +34 -29
  114. package/corpus/templates/design/actions/insert-design-native-asset.ts +129 -32
  115. package/corpus/templates/design/actions/insert-figma-library-asset.ts +35 -30
  116. package/corpus/templates/design/actions/present-design-variants.ts +7 -2
  117. package/corpus/templates/design/actions/remove-motion-timeline.ts +31 -36
  118. package/corpus/templates/design/actions/update-file.ts +190 -75
  119. package/corpus/templates/design/app/components/design/BreakpointBar.tsx +403 -0
  120. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +469 -222
  121. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +708 -53
  122. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +83 -3
  123. package/corpus/templates/design/app/components/design/EditPanel.tsx +1091 -6948
  124. package/corpus/templates/design/app/components/design/LayersPanel.tsx +419 -85
  125. package/corpus/templates/design/app/components/design/MotionDock.tsx +1124 -103
  126. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2610 -1398
  127. package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +22 -0
  128. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2182 -235
  129. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +187 -11
  130. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +376 -1
  131. package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +746 -0
  132. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +17 -0
  133. package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +1 -1
  134. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +1 -3
  135. package/corpus/templates/design/app/components/design/code-workbench/quickinput/QuickInput.tsx +1 -1
  136. package/corpus/templates/design/app/components/design/edit-panel/appearance-properties.tsx +518 -0
  137. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +562 -0
  138. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +763 -0
  139. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +94 -0
  140. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +780 -0
  141. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +446 -0
  142. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +79 -0
  143. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +315 -0
  144. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +344 -0
  145. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +594 -0
  146. package/corpus/templates/design/app/components/design/edit-panel/frame-presets-panel.tsx +119 -0
  147. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +248 -0
  148. package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +45 -0
  149. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +693 -0
  150. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +724 -0
  151. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +207 -0
  152. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +628 -0
  153. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +91 -0
  154. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +649 -0
  155. package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +118 -0
  156. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +88 -0
  157. package/corpus/templates/design/app/components/design/edit-panel/transform-helpers.ts +100 -0
  158. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +117 -0
  159. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +501 -0
  160. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +91 -40
  161. package/corpus/templates/design/app/components/design/inspector/BreakpointOverrideIndicator.tsx +82 -0
  162. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +189 -87
  163. package/corpus/templates/design/app/components/design/inspector/GlslShaderPanel.tsx +1097 -0
  164. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +44 -0
  165. package/corpus/templates/design/app/components/design/inspector/InteractionStatePanel.tsx +258 -0
  166. package/corpus/templates/design/app/components/design/inspector/MotionKeyframeDiamond.tsx +144 -0
  167. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +99 -9
  168. package/corpus/templates/design/app/components/design/inspector/frame-size-presets.ts +116 -0
  169. package/corpus/templates/design/app/components/design/inspector/index.ts +26 -0
  170. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +29 -0
  171. package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +171 -0
  172. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +220 -0
  173. package/corpus/templates/design/app/components/design/multi-screen/chrome-transitions.ts +35 -0
  174. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +134 -0
  175. package/corpus/templates/design/app/components/design/multi-screen/culling.ts +185 -0
  176. package/corpus/templates/design/app/components/design/multi-screen/gradient-overlay-geometry.ts +120 -0
  177. package/corpus/templates/design/app/components/design/multi-screen/types.ts +892 -0
  178. package/corpus/templates/design/app/components/design/multi-screen/vector-edit-geometry.ts +21 -0
  179. package/corpus/templates/design/app/components/design/types.ts +15 -0
  180. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +109 -5
  181. package/corpus/templates/design/app/global.css +8 -8
  182. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +259 -2
  183. package/corpus/templates/design/app/i18n/zh-TW.ts +163 -9
  184. package/corpus/templates/design/app/i18n-breakpoints.ts +205 -0
  185. package/corpus/templates/design/app/i18n-data.ts +1664 -152
  186. package/corpus/templates/design/app/i18n-motion.ts +563 -0
  187. package/corpus/templates/design/app/lib/design-import.ts +73 -0
  188. package/corpus/templates/design/app/pages/Index.tsx +41 -22
  189. package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +1 -1
  190. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +1 -1
  191. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +1 -1
  192. package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +1 -1
  193. package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +1 -1
  194. package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +1 -1
  195. package/corpus/templates/design/changelog/2026-07-05-designs-get-ai-generated-names-from-your-prompt.md +6 -0
  196. package/corpus/templates/design/changelog/2026-07-05-local-code-screens-keep-their-connection-when-duplicated-and.md +6 -0
  197. package/corpus/templates/design/changelog/2026-07-05-press-a-to-draw-screens-on-the-canvas-inside-a-screen-it-dr.md +6 -0
  198. package/corpus/templates/design/changelog/2026-07-06-3d-rotation-and-perspective-controls-on-every-element.md +6 -0
  199. package/corpus/templates/design/changelog/2026-07-06-ai-generated-and-duplicated-screens-now-get-their-editor-sel.md +6 -0
  200. package/corpus/templates/design/changelog/2026-07-06-applying-shaders-and-fills-in-quick-succession-can-no-longer.md +6 -0
  201. package/corpus/templates/design/changelog/2026-07-06-breakpoint-frames-in-the-overview-now-support-click-to-selec.md +6 -0
  202. package/corpus/templates/design/changelog/2026-07-06-breakpoint-segments-in-the-inspector-now-show-a-device-icon-.md +6 -0
  203. package/corpus/templates/design/changelog/2026-07-06-breakpoint-targeting-now-lives-in-one-compact-control-in-the.md +6 -0
  204. package/corpus/templates/design/changelog/2026-07-06-canvas-edits-can-no-longer-be-overwritten-by-stale-collabora.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-06-canvas-edits-inside-screens-save-reliably-again.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-06-cleaner-inspector-removed-the-ask-ai-box-from-the-design-tab.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-06-clicking-empty-canvas-space-now-deselects-an-element-inside-.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-06-dragging-an-element-onto-a-rectangle-now-nests-it-inside-wit.md +6 -0
  209. package/corpus/templates/design/changelog/2026-07-06-dragging-screens-and-objects-on-the-canvas-is-now-much-smoot.md +6 -0
  210. package/corpus/templates/design/changelog/2026-07-06-drawing-a-shape-or-text-on-the-canvas-no-longer-flashes-away.md +6 -0
  211. package/corpus/templates/design/changelog/2026-07-06-dropping-an-element-between-two-others-inside-a-screen-now-s.md +6 -0
  212. package/corpus/templates/design/changelog/2026-07-06-duplicating-a-screen-now-places-the-copy-exactly-where-you-d.md +6 -0
  213. package/corpus/templates/design/changelog/2026-07-06-edit-gradients-directly-on-elements-inside-screens-scale-str.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-06-edits-from-collaborators-and-the-agent-now-update-the-canvas.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-06-fixed-a-bug-where-most-style-position-and-text-edits-briefly.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-06-fixed-a-confusing-raw-error-message-when-a-file-upload-fails.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-06-fixed-a-rare-case-where-an-edit-could-be-silently-overwritte.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-06-fixed-inserted-assets-sometimes-disappearing-reappearing-or-.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-06-fixed-inspector-value-scrubbing-producing-erratic-numbers-an.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-06-fixed-padding-gap-and-min-max-size-scrubbing-not-updating-th.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-06-fixed-text-stroke-color-turning-black-and-glyphs-disappearin.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-06-fixed-the-collapsed-effects-and-stroke-inspector-sections-sh.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-06-fixed-the-new-design-dialog-closing-unexpectedly-when-using-.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-06-fixed-the-typography-section-missing-when-selecting-text-nes.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-06-full-view-no-longer-flashes-back-to-the-overview-on-entry-no.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-06-keyframe-animation-timeline-per-property-keyframes-easing-cu.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-06-moving-restyling-and-scrubbing-values-on-elements-in-ai-gene.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-06-padding-and-corner-radius-value-scrubbing-now-snaps-to-whole.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-06-padding-handles-now-only-resize-on-the-handle-line-itself-sh.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-06-preview-and-edit-hover-focus-and-pressed-states-with-real-cs.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-06-promoting-an-element-to-a-component-now-works-correctly-for-.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-06-reduced-url-history-churn-and-selection-overlay-flicker-whil.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-06-removed-the-decorative-dot-before-screen-names-in-the-overvi.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-06-responsive-breakpoints-edit-at-any-width-with-cascading-over.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-06-screen-labels-no-longer-turn-blue-when-you-hover-inside-the-.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-06-scrolling-a-screen-with-an-element-selected-stays-smooth-on-.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-06-selecting-multiple-elements-and-dragging-now-moves-the-whole.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-06-selection-borders-handles-and-value-readouts-now-stay-the-sa.md +6 -0
  239. package/corpus/templates/design/changelog/2026-07-06-selection-handles-and-editing-chrome-now-stay-a-constant-siz.md +6 -0
  240. package/corpus/templates/design/changelog/2026-07-06-shader-fills-and-effects-ai-authored-or-preset-glsl-shaders-.md +6 -0
  241. package/corpus/templates/design/changelog/2026-07-06-text-drawn-on-the-canvas-now-defaults-to-white-and-inter-in-.md +6 -0
  242. package/corpus/templates/design/changelog/2026-07-06-text-dropped-into-a-shape-now-stays-readable-instead-of-disa.md +6 -0
  243. package/corpus/templates/design/changelog/2026-07-06-the-add-tweak-controls-prompt-no-longer-jumps-to-the-corner-.md +6 -0
  244. package/corpus/templates/design/changelog/2026-07-06-the-padding-value-readout-now-stays-visible-while-you-hover-.md +6 -0
  245. package/corpus/templates/design/changelog/2026-07-06-the-selected-element-attachment-now-clears-from-the-chat-com.md +6 -0
  246. package/corpus/templates/design/changelog/2026-07-06-the-zoom-menu-is-more-compact-with-tight-rows-and-right-alig.md +6 -0
  247. package/corpus/templates/design/changelog/2026-07-06-undo-and-redo-no-longer-flash-the-screen-changes-now-apply-i.md +6 -0
  248. package/corpus/templates/design/changelog/2026-07-06-zoom-presets-like-zoom-to-50-now-stay-on-the-focused-screen-.md +6 -0
  249. package/corpus/templates/design/server/lib/import-design-files.ts +11 -2
  250. package/corpus/templates/design/server/source-workspace.ts +139 -59
  251. package/corpus/templates/design/shared/board-file.ts +11 -2
  252. package/corpus/templates/design/shared/breakpoint-media.ts +543 -0
  253. package/corpus/templates/design/shared/canvas-math.ts +354 -35
  254. package/corpus/templates/design/shared/code-layer.ts +277 -8
  255. package/corpus/templates/design/shared/design-state.ts +11 -4
  256. package/corpus/templates/design/shared/interaction-states.ts +548 -0
  257. package/corpus/templates/design/shared/motion-compiler.ts +186 -23
  258. package/corpus/templates/design/shared/motion-easing.ts +294 -0
  259. package/corpus/templates/design/shared/motion-timeline.ts +400 -34
  260. package/corpus/templates/design/shared/prompt-title.ts +95 -0
  261. package/corpus/templates/design/shared/responsive-classes.ts +448 -10
  262. package/corpus/templates/design/shared/screen-annotation.ts +46 -0
  263. package/corpus/templates/design/shared/shader-fills.ts +1009 -0
  264. package/corpus/templates/design/shared/shader-presets.ts +852 -4
  265. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +15 -2
  266. package/corpus/templates/slides/app/components/editor/ImportButton.tsx +10 -1
  267. package/corpus/templates/slides/app/lib/upload-response.ts +71 -0
  268. package/dist/client/AgentPanel.js +1 -1
  269. package/dist/client/AgentPanel.js.map +1 -1
  270. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  271. package/dist/client/rich-markdown-editor/useCollabReconcile.js +11 -6
  272. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  273. package/dist/client/use-action.d.ts +3 -0
  274. package/dist/client/use-action.d.ts.map +1 -1
  275. package/dist/client/use-action.js +34 -3
  276. package/dist/client/use-action.js.map +1 -1
  277. package/dist/client/use-db-sync.d.ts +4 -0
  278. package/dist/client/use-db-sync.d.ts.map +1 -1
  279. package/dist/client/use-db-sync.js +37 -18
  280. package/dist/client/use-db-sync.js.map +1 -1
  281. package/dist/collab/routes.d.ts +1 -1
  282. package/dist/collab/struct-routes.d.ts +1 -1
  283. package/dist/db/create-get-db.d.ts +25 -0
  284. package/dist/db/create-get-db.d.ts.map +1 -1
  285. package/dist/db/create-get-db.js +25 -2
  286. package/dist/db/create-get-db.js.map +1 -1
  287. package/dist/observability/routes.d.ts +3 -3
  288. package/dist/secrets/routes.d.ts +9 -9
  289. package/dist/server/framework-request-handler.d.ts.map +1 -1
  290. package/dist/server/framework-request-handler.js +37 -1
  291. package/dist/server/framework-request-handler.js.map +1 -1
  292. package/package.json +1 -1
  293. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +0 -153
  294. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +0 -133
  295. package/corpus/templates/design/app/pages/DesignEditor.tsx +0 -22924
@@ -3,16 +3,17 @@ import {
3
3
  agentEnterDocument,
4
4
  agentLeaveDocument,
5
5
  agentUpdateSelection,
6
- applyText,
7
- getText,
8
- hasCollabState,
9
- seedFromText,
10
6
  } from "@agent-native/core/collab";
11
7
  import { accessFilter, assertAccess } from "@agent-native/core/sharing";
12
8
  import { and, eq } from "drizzle-orm";
13
9
  import { z } from "zod";
14
10
 
15
11
  import { getDb, schema } from "../server/db/index.js";
12
+ import {
13
+ readLiveSourceFile,
14
+ writeInlineSourceFile,
15
+ type SourceWorkspaceFile,
16
+ } from "../server/source-workspace.js";
16
17
  import {
17
18
  applyVisualEdit,
18
19
  type AutoLayoutEditIntent,
@@ -24,7 +25,12 @@ import {
24
25
  } from "../shared/code-layer.js";
25
26
  import { agentSelectionDescriptor } from "../shared/collab-selection.js";
26
27
  import type { TailwindBreakpointPrefix } from "../shared/design-state.js";
27
- import { utilityStem, widthToPrefix } from "../shared/responsive-classes.js";
28
+ import {
29
+ breakpointUpperBoundPx,
30
+ planBreakpointStyleWrite,
31
+ utilityStem,
32
+ widthToPrefix,
33
+ } from "../shared/responsive-classes.js";
28
34
 
29
35
  /**
30
36
  * Short human-readable label describing an edit intent, shown next to the
@@ -38,6 +44,7 @@ function editIntentLabel(intent: EditIntent): string {
38
44
  return "Editing style";
39
45
  case "class":
40
46
  case "responsive-class":
47
+ case "breakpoint-style":
41
48
  return "Editing styles";
42
49
  case "moveNode":
43
50
  return "Moving element";
@@ -154,6 +161,139 @@ function scopeClassIntentToBreakpoint(
154
161
  return intent;
155
162
  }
156
163
 
164
+ /**
165
+ * Convert a `class` or `style` intent into the equivalent Framer-scoped edit
166
+ * for a desktop-down max-width bound (§6.4 breakpoint bar semantics):
167
+ *
168
+ * - `class` add/replace/remove → `responsive-class` with `maxWidthPx`
169
+ * (writes/removes a `max-[<bound>px]:` scoped token).
170
+ * - `style` → the single class-vs-media decision (`planBreakpointStyleWrite`):
171
+ * Tailwind-utility values become scoped classes; raw CSS values become
172
+ * managed `@media (max-width: <bound>px)` rules via `breakpoint-style`.
173
+ * - Everything else passes through unchanged.
174
+ */
175
+ function scopeIntentToFramerBound(
176
+ intent: EditIntent,
177
+ maxWidthPx: number,
178
+ ): EditIntent {
179
+ if (intent.kind === "class") {
180
+ if (intent.operation === "add" || intent.operation === "replace") {
181
+ const tokens =
182
+ intent.operation === "replace"
183
+ ? intent.to
184
+ ? [intent.to]
185
+ : []
186
+ : (intent.classNames ?? (intent.className ? [intent.className] : []));
187
+ if (tokens.length !== 1 || !tokens[0]) return intent;
188
+ return {
189
+ kind: "responsive-class",
190
+ target: intent.target,
191
+ prefix: "base", // ignored when maxWidthPx is set
192
+ maxWidthPx,
193
+ operation: intent.operation,
194
+ utility: tokens[0],
195
+ };
196
+ }
197
+ if (intent.operation === "remove") {
198
+ const tokens =
199
+ intent.classNames ?? (intent.className ? [intent.className] : []);
200
+ if (tokens.length !== 1 || !tokens[0]) return intent;
201
+ return {
202
+ kind: "responsive-class",
203
+ target: intent.target,
204
+ prefix: "base",
205
+ maxWidthPx,
206
+ operation: "remove",
207
+ stem: stemFromToken(tokens[0]),
208
+ };
209
+ }
210
+ // "set" — no per-breakpoint analog.
211
+ return intent;
212
+ }
213
+
214
+ if (intent.kind === "style") {
215
+ const plan = planBreakpointStyleWrite({
216
+ property: intent.property,
217
+ value: intent.value,
218
+ upperBoundPx: maxWidthPx,
219
+ });
220
+ if (plan.mode === "class") {
221
+ return {
222
+ kind: "responsive-class",
223
+ target: intent.target,
224
+ prefix: "base",
225
+ maxWidthPx: plan.boundPx,
226
+ operation: "replace",
227
+ utility: plan.utility,
228
+ };
229
+ }
230
+ if (plan.mode === "media") {
231
+ return {
232
+ kind: "breakpoint-style",
233
+ target: intent.target,
234
+ maxWidthPx: plan.maxWidthPx,
235
+ property: plan.property,
236
+ value: plan.value,
237
+ operation: "set",
238
+ };
239
+ }
240
+ return intent;
241
+ }
242
+
243
+ return intent;
244
+ }
245
+
246
+ /**
247
+ * Resolve the Framer desktop-down bound for a design-file edit from the
248
+ * design's stored breakpoint set (+ the edited screen's primary width).
249
+ *
250
+ * - `{ kind: "bound" }` — a wider frame exists; scope below it.
251
+ * - `{ kind: "base" }` — the active frame IS the widest context; edits
252
+ * belong to the base layer (Framer semantics).
253
+ * - `{ kind: "unknown" }` — the design has no breakpoint set; callers fall
254
+ * back to the legacy min-width prefix behaviour.
255
+ */
256
+ function resolveFramerBoundFromDesignData(
257
+ designData: string | null,
258
+ fileId: string,
259
+ activeFrameWidthPx: number,
260
+ ): { kind: "bound"; boundPx: number } | { kind: "base" } | { kind: "unknown" } {
261
+ if (!designData) return { kind: "unknown" };
262
+ try {
263
+ const parsed = JSON.parse(designData) as Record<string, unknown>;
264
+ const rawSet = parsed.breakpointSet as
265
+ | { breakpoints?: Array<{ widthPx?: unknown }> }
266
+ | undefined;
267
+ const widths = Array.isArray(rawSet?.breakpoints)
268
+ ? rawSet.breakpoints
269
+ .map((bp) => bp?.widthPx)
270
+ .filter(
271
+ (width): width is number =>
272
+ typeof width === "number" && Number.isFinite(width),
273
+ )
274
+ : [];
275
+ if (widths.length === 0) return { kind: "unknown" };
276
+
277
+ const metadataByFileId = parsed.screenMetadata as
278
+ | Record<string, { width?: unknown } | undefined>
279
+ | undefined;
280
+ const rawScreenWidth = metadataByFileId?.[fileId]?.width;
281
+ const screenWidthPx =
282
+ typeof rawScreenWidth === "number" && Number.isFinite(rawScreenWidth)
283
+ ? rawScreenWidth
284
+ : null;
285
+
286
+ const boundPx = breakpointUpperBoundPx(
287
+ widths,
288
+ activeFrameWidthPx,
289
+ screenWidthPx,
290
+ );
291
+ return boundPx === null ? { kind: "base" } : { kind: "bound", boundPx };
292
+ } catch {
293
+ return { kind: "unknown" };
294
+ }
295
+ }
296
+
157
297
  function parseJsonString(value: unknown): unknown {
158
298
  if (typeof value !== "string") return value;
159
299
  try {
@@ -226,6 +366,30 @@ const intentSchema = z.preprocess(
226
366
  from: z.string().optional(),
227
367
  to: z.string().optional(),
228
368
  }),
369
+ z.object({
370
+ kind: z.literal("breakpoint-style"),
371
+ target: targetSchema,
372
+ maxWidthPx: z
373
+ .number()
374
+ .int()
375
+ .positive()
376
+ .describe(
377
+ "Inclusive upper viewport bound (px). The declaration persists as a managed '@media (max-width: <bound>px)' rule in the <style data-agent-native-breakpoints> block — the fallback for values responsive class prefixes can't express (exact px positions, rgb()/calc() values). Use breakpointUpperBoundPx semantics: just below the next-wider breakpoint frame.",
378
+ ),
379
+ property: z
380
+ .string()
381
+ .describe("CSS property to set (camelCase or kebab-case)."),
382
+ value: z
383
+ .string()
384
+ .optional()
385
+ .describe("CSS value. Required for operation 'set'."),
386
+ operation: z
387
+ .enum(["set", "remove"])
388
+ .optional()
389
+ .describe(
390
+ "'set' (default) writes/overwrites the scoped declaration; 'remove' deletes it so the base value cascades back down.",
391
+ ),
392
+ }),
229
393
  z.object({
230
394
  kind: z.literal("textContent"),
231
395
  target: targetSchema,
@@ -290,28 +454,16 @@ const intentSchema = z.preprocess(
290
454
  ]),
291
455
  );
292
456
 
293
- async function liveContent(
294
- fileId: string,
295
- storedContent: string,
296
- ): Promise<string> {
297
- try {
298
- if (await hasCollabState(fileId)) {
299
- const live = await getText(fileId, "content");
300
- if (typeof live === "string") return live;
301
- }
302
- } catch {
303
- // Collab reads are best-effort; SQL content remains the fallback.
304
- }
305
- return storedContent;
306
- }
307
-
308
457
  async function resolveEditableDesignFile(
309
458
  source: VisualEditActionSource,
310
459
  ): Promise<{
311
460
  id: string;
312
461
  designId: string;
313
462
  filename: string;
463
+ fileType: string;
314
464
  content: string;
465
+ versionHash: string;
466
+ designData: string | null;
315
467
  codeLayerSource: CodeLayerSource;
316
468
  }> {
317
469
  if (!source.fileId && !source.designId) {
@@ -340,6 +492,7 @@ async function resolveEditableDesignFile(
340
492
  filename: schema.designFiles.filename,
341
493
  fileType: schema.designFiles.fileType,
342
494
  content: schema.designFiles.content,
495
+ designData: schema.designs.data,
343
496
  })
344
497
  .from(schema.designFiles)
345
498
  .innerJoin(
@@ -368,11 +521,32 @@ async function resolveEditableDesignFile(
368
521
 
369
522
  await assertAccess("design", file.designId, "editor");
370
523
 
524
+ // Read the live (collab-authoritative, not just SQL-stored) content and
525
+ // capture its versionHash so the eventual persist can be conditioned on
526
+ // this exact base still being current — see persistDesignFileEdit below.
527
+ // Same read helper the 8 sibling actions (insert-design-native-asset.ts,
528
+ // insert-asset.ts, etc.) migrated to, closing the write-race window where
529
+ // a concurrent editor's change landed between this read and the raw
530
+ // unconditional write this action used to do.
531
+ const workspaceFile: SourceWorkspaceFile = {
532
+ id: file.id,
533
+ designId: file.designId,
534
+ filename: file.filename,
535
+ fileType: file.fileType,
536
+ content: file.content,
537
+ createdAt: null,
538
+ updatedAt: null,
539
+ };
540
+ const live = await readLiveSourceFile(workspaceFile);
541
+
371
542
  return {
372
543
  id: file.id,
373
544
  designId: file.designId,
374
545
  filename: file.filename,
375
- content: await liveContent(file.id, file.content ?? ""),
546
+ fileType: file.fileType,
547
+ content: live.content,
548
+ versionHash: live.versionHash,
549
+ designData: file.designData ?? null,
376
550
  codeLayerSource: {
377
551
  kind: "design-file",
378
552
  designId: file.designId,
@@ -386,30 +560,27 @@ async function resolveEditableDesignFile(
386
560
  async function persistDesignFileEdit(file: {
387
561
  id: string;
388
562
  designId: string;
563
+ filename: string;
564
+ fileType: string;
389
565
  content: string;
566
+ expectedVersionHash: string;
390
567
  }): Promise<void> {
391
- await assertAccess("design", file.designId, "editor");
392
-
393
- const db = getDb();
394
- const now = new Date().toISOString();
395
-
396
568
  agentEnterDocument(file.id);
397
569
  try {
398
- await db
399
- .update(schema.designFiles)
400
- .set({ content: file.content, updatedAt: now })
401
- .where(eq(schema.designFiles.id, file.id));
402
-
403
- if (await hasCollabState(file.id)) {
404
- await applyText(file.id, file.content, "content", "agent");
405
- } else {
406
- await seedFromText(file.id, file.content);
407
- }
408
-
409
- await db
410
- .update(schema.designs)
411
- .set({ updatedAt: now })
412
- .where(eq(schema.designs.id, file.designId));
570
+ await writeInlineSourceFile({
571
+ designId: file.designId,
572
+ file: {
573
+ id: file.id,
574
+ designId: file.designId,
575
+ filename: file.filename,
576
+ fileType: file.fileType,
577
+ content: file.content,
578
+ createdAt: null,
579
+ updatedAt: null,
580
+ },
581
+ content: file.content,
582
+ expectedVersionHash: file.expectedVersionHash,
583
+ });
413
584
  } finally {
414
585
  agentLeaveDocument(file.id);
415
586
  }
@@ -419,7 +590,9 @@ export default defineAction({
419
590
  description:
420
591
  "Apply one deterministic visual edit to a code-backed HTML design layer. " +
421
592
  "Supports safe inline style, class, and leaf textContent edits on inline/SQL HTML files; escalates ambiguous or structural edits with PatchResult statuses. " +
422
- "Pass activeBreakpoint (or activeFrameWidthPx) to scope a class edit to a specific Tailwind responsive prefix; omit for global (backward-compatible) behaviour.",
593
+ "Responsive editing (§6.4): pass activeFrameWidthPx (the active breakpoint frame width, matching the UI's breakpoint bar) to scope class AND style edits Framer-style overrides apply below the next-wider frame and cascade down; the widest frame is the base. " +
594
+ "Raw CSS values persist as managed @media rules (<style data-agent-native-breakpoints>); Tailwind-utility values become max-[<bound>px]: classes. " +
595
+ "Pass activeBreakpoint to force legacy min-width prefix scoping for class edits, or maxWidthPx for an explicit desktop-down bound. Omit all three for base (global) behaviour.",
423
596
  schema: z.object({
424
597
  source: sourceSchema.describe(
425
598
  "Edit source. Use kind=design-file with designId/filename or fileId to persist into SQL; kind=inline-html with html for a preview-only patch.",
@@ -446,7 +619,16 @@ export default defineAction({
446
619
  .optional()
447
620
  .nullable()
448
621
  .describe(
449
- "Canvas frame width in pixels. When activeBreakpoint is not set, this is used to derive the Tailwind responsive prefix (via widthToPrefix) and scope class edits accordingly. Ignored when activeBreakpoint is provided.",
622
+ "Active breakpoint frame width in pixels — matches the UI's breakpoint bar. For design-file sources whose design has a breakpoint set, 'class' AND 'style' intents are scoped Framer-style: overrides apply below the next-wider frame (max-[<bound>px]: classes, or managed @media rules for raw CSS values); the widest frame is the base and writes unscoped. When the design has no breakpoint set (or for inline-html sources) class edits fall back to the legacy min-width Tailwind prefix via widthToPrefix. Ignored when activeBreakpoint is provided.",
623
+ ),
624
+ maxWidthPx: z
625
+ .number()
626
+ .int()
627
+ .positive()
628
+ .optional()
629
+ .nullable()
630
+ .describe(
631
+ "Explicit Framer desktop-down bound (px): scope this edit to apply at viewport widths <= this value. Overrides activeBreakpoint/activeFrameWidthPx derivation. Applies to 'class' and 'style' intents.",
450
632
  ),
451
633
  }),
452
634
  run: async ({
@@ -455,22 +637,36 @@ export default defineAction({
455
637
  includeContent,
456
638
  activeBreakpoint,
457
639
  activeFrameWidthPx,
640
+ maxWidthPx,
458
641
  }) => {
459
642
  const actionSource = source as VisualEditActionSource;
460
643
  let editIntent = intent as EditIntent;
461
644
 
462
- // Breakpoint scoping: when the caller specifies an active breakpoint (or
463
- // frame width) and the intent is a plain `"class"` edit, convert it to a
464
- // `"responsive-class"` edit scoped to the appropriate Tailwind prefix.
465
- // All other intent kinds (style, textContent, moveNode, responsive-class)
466
- // are passed through unchanged the caller either already supplied the
467
- // correct prefix (responsive-class) or does not need one (the others).
468
- const activePrefix = resolveActivePrefix(
469
- activeBreakpoint,
470
- activeFrameWidthPx,
471
- );
472
- if (activePrefix !== null && editIntent.kind === "class") {
473
- editIntent = scopeClassIntentToBreakpoint(editIntent, activePrefix);
645
+ // Breakpoint scoping precedence (§6.4):
646
+ //
647
+ // 1. Explicit `maxWidthPx` param Framer desktop-down scope for `class`
648
+ // AND `style` intents (max-[<bound>px]: classes / managed @media).
649
+ // 2. Explicit `activeBreakpoint` prefix legacy min-width Tailwind
650
+ // prefix scoping for `class` intents (backward-compatible).
651
+ // 3. `activeFrameWidthPx` only → design-file sources resolve the Framer
652
+ // bound from the design's breakpoint set (below, after the file is
653
+ // loaded); other sources fall back to the legacy prefix path.
654
+ if (
655
+ maxWidthPx != null &&
656
+ (editIntent.kind === "class" || editIntent.kind === "style")
657
+ ) {
658
+ editIntent = scopeIntentToFramerBound(editIntent, maxWidthPx);
659
+ } else if (
660
+ actionSource.kind !== "design-file" ||
661
+ activeBreakpoint != null
662
+ ) {
663
+ const activePrefix = resolveActivePrefix(
664
+ activeBreakpoint,
665
+ activeFrameWidthPx,
666
+ );
667
+ if (activePrefix !== null && editIntent.kind === "class") {
668
+ editIntent = scopeClassIntentToBreakpoint(editIntent, activePrefix);
669
+ }
474
670
  }
475
671
 
476
672
  if (actionSource.kind === "inline-html") {
@@ -512,6 +708,34 @@ export default defineAction({
512
708
  }
513
709
 
514
710
  const file = await resolveEditableDesignFile(actionSource);
711
+
712
+ // §6.4 — design-file Framer scoping resolved from the stored breakpoint
713
+ // set (see precedence note above): the bound is just below the
714
+ // next-wider frame; the widest frame is the base and writes unscoped.
715
+ if (
716
+ maxWidthPx == null &&
717
+ activeBreakpoint == null &&
718
+ activeFrameWidthPx != null &&
719
+ (editIntent.kind === "class" || editIntent.kind === "style")
720
+ ) {
721
+ const bound = resolveFramerBoundFromDesignData(
722
+ file.designData,
723
+ file.id,
724
+ activeFrameWidthPx,
725
+ );
726
+ if (bound.kind === "bound") {
727
+ editIntent = scopeIntentToFramerBound(editIntent, bound.boundPx);
728
+ } else if (bound.kind === "unknown" && editIntent.kind === "class") {
729
+ // No breakpoint set on this design — legacy min-width prefix path.
730
+ const activePrefix = resolveActivePrefix(null, activeFrameWidthPx);
731
+ if (activePrefix !== null) {
732
+ editIntent = scopeClassIntentToBreakpoint(editIntent, activePrefix);
733
+ }
734
+ }
735
+ // bound.kind === "base": the active frame IS the widest context —
736
+ // the edit stays a plain base write that cascades down.
737
+ }
738
+
515
739
  const patch = applyVisualEdit(file.content, editIntent, {
516
740
  source: file.codeLayerSource,
517
741
  });
@@ -535,7 +759,10 @@ export default defineAction({
535
759
  await persistDesignFileEdit({
536
760
  id: file.id,
537
761
  designId: file.designId,
762
+ filename: file.filename,
763
+ fileType: file.fileType,
538
764
  content: patch.content,
765
+ expectedVersionHash: file.versionHash,
539
766
  });
540
767
  }
541
768
 
@@ -28,15 +28,7 @@
28
28
  */
29
29
 
30
30
  import { defineAction } from "@agent-native/core";
31
- import {
32
- agentEnterDocument,
33
- agentLeaveDocument,
34
- agentUpdateSelection,
35
- applyText,
36
- getText,
37
- hasCollabState,
38
- seedFromText,
39
- } from "@agent-native/core/collab";
31
+ import { agentUpdateSelection } from "@agent-native/core/collab";
40
32
  import {
41
33
  accessFilter,
42
34
  assertAccess,
@@ -47,6 +39,11 @@ import { z } from "zod";
47
39
 
48
40
  import { getDb, schema } from "../server/db/index.js";
49
41
  import "../server/db/index.js"; // ensure registerShareableResource runs
42
+ import {
43
+ readLiveSourceFile,
44
+ writeInlineSourceFile,
45
+ type SourceWorkspaceFile,
46
+ } from "../server/source-workspace.js";
50
47
  import { resolveSourceCapabilities } from "../shared/capability-resolver.js";
51
48
  import { buildCodeLayerProjection } from "../shared/code-layer.js";
52
49
  import type { CodeLayerNode, CodeLayerSource } from "../shared/code-layer.js";
@@ -209,54 +206,6 @@ export function applyComponentAnnotations(
209
206
  };
210
207
  }
211
208
 
212
- // ─── DB helpers ───────────────────────────────────────────────────────────────
213
-
214
- async function liveContent(
215
- fileId: string,
216
- storedContent: string,
217
- ): Promise<string> {
218
- try {
219
- if (await hasCollabState(fileId)) {
220
- const live = await getText(fileId, "content");
221
- if (typeof live === "string") return live;
222
- }
223
- } catch {
224
- // Collab reads are best-effort; SQL content is the fallback.
225
- }
226
- return storedContent;
227
- }
228
-
229
- async function persistEdit(file: {
230
- id: string;
231
- designId: string;
232
- content: string;
233
- }): Promise<void> {
234
- await assertAccess("design", file.designId, "editor");
235
- const db = getDb();
236
- const now = new Date().toISOString();
237
-
238
- agentEnterDocument(file.id);
239
- try {
240
- await db
241
- .update(schema.designFiles)
242
- .set({ content: file.content, updatedAt: now })
243
- .where(eq(schema.designFiles.id, file.id));
244
-
245
- if (await hasCollabState(file.id)) {
246
- await applyText(file.id, file.content, "content", "agent");
247
- } else {
248
- await seedFromText(file.id, file.content);
249
- }
250
-
251
- await db
252
- .update(schema.designs)
253
- .set({ updatedAt: now })
254
- .where(eq(schema.designs.id, file.designId));
255
- } finally {
256
- agentLeaveDocument(file.id);
257
- }
258
- }
259
-
260
209
  // ─── Action ───────────────────────────────────────────────────────────────────
261
210
 
262
211
  export default defineAction({
@@ -371,7 +320,26 @@ export default defineAction({
371
320
 
372
321
  if (!file) throw new Error("Design HTML file not found.");
373
322
 
374
- const html = await liveContent(file.id, file.content ?? "");
323
+ // Read the LIVE base (collab text when present, else the SQL row) right
324
+ // before transforming, and carry its versionHash through to the write
325
+ // below. writeInlineSourceFile re-reads the live text immediately before
326
+ // its own applyText/DB write and rejects if it no longer matches this
327
+ // hash — closing the race window where a concurrent editor/agent write
328
+ // lands between this read and the persist (the same stale-diff-base bug
329
+ // fixed for insert-design-native-asset.ts and insert-asset.ts: a diff/patch
330
+ // computed from a stale base, unconditionally persisted, corrupts or
331
+ // drops the other writer's change).
332
+ const workspaceFile: SourceWorkspaceFile = {
333
+ id: file.id,
334
+ designId: file.designId,
335
+ filename: file.filename ?? "",
336
+ fileType: "html",
337
+ content: file.content,
338
+ createdAt: null,
339
+ updatedAt: null,
340
+ };
341
+ const live = await readLiveSourceFile(workspaceFile);
342
+ const html = live.content;
375
343
 
376
344
  // ── Resolve node ─────────────────────────────────────────────────────────
377
345
  const codeLayerSource: CodeLayerSource = {
@@ -410,10 +378,11 @@ export default defineAction({
410
378
 
411
379
  // ── Persist ──────────────────────────────────────────────────────────────
412
380
  if (changed) {
413
- await persistEdit({
414
- id: file.id,
381
+ await writeInlineSourceFile({
415
382
  designId: file.designId,
383
+ file: workspaceFile,
416
384
  content: patchedContent,
385
+ expectedVersionHash: live.versionHash,
417
386
  });
418
387
 
419
388
  agentUpdateSelection(file.id, {
@@ -6,6 +6,7 @@ import { nanoid } from "nanoid";
6
6
  import { z } from "zod";
7
7
 
8
8
  import { getDb, schema } from "../server/db/index.js";
9
+ import { annotateScreenHtmlForPersist } from "../shared/screen-annotation.js";
9
10
 
10
11
  export default defineAction({
11
12
  description:
@@ -56,18 +57,24 @@ export default defineAction({
56
57
  const id = nanoid();
57
58
  const now = new Date().toISOString();
58
59
 
60
+ // Stamp missing data-agent-native-node-id attributes before persisting so
61
+ // the new screen is fully addressable by id-keyed editor operations from
62
+ // the moment it's created, instead of depending on a client-side backfill
63
+ // the first time someone opens it.
64
+ const annotatedContent = annotateScreenHtmlForPersist(content, fileType);
65
+
59
66
  await db.insert(schema.designFiles).values({
60
67
  id,
61
68
  designId,
62
69
  filename,
63
70
  fileType: fileType ?? "html",
64
- content,
71
+ content: annotatedContent,
65
72
  createdAt: now,
66
73
  updatedAt: now,
67
74
  });
68
75
 
69
76
  // Seed collab state for the new file
70
- await seedFromText(id, content);
77
+ await seedFromText(id, annotatedContent);
71
78
 
72
79
  // Update the design's updatedAt timestamp
73
80
  await db
@@ -9,6 +9,7 @@ import { nanoid } from "nanoid";
9
9
  import { z } from "zod";
10
10
 
11
11
  import { getDb, schema } from "../server/db/index.js";
12
+ import { annotateScreenHtmlForPersist } from "../shared/screen-annotation.js";
12
13
 
13
14
  export default defineAction({
14
15
  description:
@@ -82,14 +83,42 @@ export default defineAction({
82
83
  updatedAt: now,
83
84
  });
84
85
 
85
- // Copy all associated files using the pre-generated IDs
86
+ // Copy all associated files using the pre-generated IDs. `content` is
87
+ // copied verbatim, including any `data-agent-native-node-id` attributes
88
+ // already stamped on the source screen — those ids are NOT regenerated
89
+ // here.
90
+ //
91
+ // This is a deliberate simplification, not an oversight: node ids are
92
+ // scoped to a single file's DOM, never looked up globally. Every
93
+ // consumer (buildCodeLayerProjection/ensureCodeLayerNodeIdsInHtml in
94
+ // shared/code-layer.ts, the MultiScreenCanvas/editor-chrome bridge
95
+ // querySelectorAll calls) resolves ids against one screen's parsed HTML
96
+ // or one iframe's contentDocument, and each design's screens render in
97
+ // their own isolated iframe — so a duplicated screen sharing ids with its
98
+ // source design is harmless: there is no code path that queries two
99
+ // screens' DOMs together. Regenerating ids here would additionally have
100
+ // to rewrite every `[data-agent-native-node-id="..."]` selector embedded
101
+ // inline by shared/motion-compiler.ts and shared/interaction-states.ts
102
+ // (motion timelines and hover/focus states target nodes by id in CSS
103
+ // baked into the same HTML string) to keep them pointing at the
104
+ // corresponding element — a real rewrite with real regression risk
105
+ // (silently dropped motion/interaction styling on duplicated screens) to
106
+ // fix a collision that has no observable effect. If a future feature
107
+ // introduces cross-design lookups keyed on node id alone (e.g. pasting
108
+ // one design's screen into another), reconsider this and add an id-remap
109
+ // pass that also rewrites those embedded CSS selectors in lockstep.
110
+ //
111
+ // Still fill in any MISSING ids (belt-and-suspenders for designs
112
+ // generated/imported before ids were stamped at creation time): this only
113
+ // adds ids where none exist and never touches an id that's already
114
+ // present, so it can't disturb the existing-id-copy contract above.
86
115
  for (const file of files) {
87
116
  await db.insert(schema.designFiles).values({
88
117
  id: idMap.get(file.id)!,
89
118
  designId: newId,
90
119
  filename: file.filename,
91
120
  fileType: file.fileType,
92
- content: file.content,
121
+ content: annotateScreenHtmlForPersist(file.content, file.fileType),
93
122
  createdAt: now,
94
123
  updatedAt: now,
95
124
  });