@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
@@ -0,0 +1,543 @@
1
+ /**
2
+ * Managed breakpoint media block for the Design Studio (§6.4).
3
+ *
4
+ * Responsive class prefixes can't express arbitrary values (exact px
5
+ * positions from canvas drags, rgb() colors, calc() sizes, …). Those
6
+ * overrides persist into a single managed
7
+ * `<style data-agent-native-breakpoints>` block as plain
8
+ * `@media (max-width: Npx)` rules — readable and editable in the Code panel,
9
+ * and rendered identically by every breakpoint frame because each frame is
10
+ * just the same document at a different viewport width.
11
+ *
12
+ * Follows the managed-block conventions of `motion-compiler.ts`:
13
+ * - **Deterministic**: same model → byte-identical CSS. Buckets are sorted
14
+ * widest bound first (so narrower ranges win by source order, matching the
15
+ * Framer desktop-down cascade), node ids and properties alphabetically.
16
+ * - **Targets by node id**: rules use `[data-agent-native-node-id="<id>"]`
17
+ * selectors — no class/id coupling.
18
+ * - **No dependencies**, pure string transforms.
19
+ */
20
+
21
+ import {
22
+ breakpointUpperBoundPx,
23
+ maxWidthOverridesForStem,
24
+ normalizeCssPropertyName,
25
+ utilityStemsForCssProperty,
26
+ } from "./responsive-classes.js";
27
+
28
+ // ─── Model ────────────────────────────────────────────────────────────────────
29
+
30
+ /**
31
+ * Parsed managed block: max-width bound (px) → node id → CSS property →
32
+ * value. Plain nested records so callers can serialize/diff cheaply.
33
+ */
34
+ export type BreakpointMediaModel = Record<
35
+ string,
36
+ Record<string, Record<string, string>>
37
+ >;
38
+
39
+ /** One flattened managed rule, as returned by `getBreakpointMediaDeclarations`. */
40
+ export interface BreakpointMediaDeclaration {
41
+ maxWidthPx: number;
42
+ nodeId: string;
43
+ property: string;
44
+ value: string;
45
+ }
46
+
47
+ // ─── Validation ──────────────────────────────────────────────────────────────
48
+
49
+ const CSS_VALUE_BREAKOUT_RE = /[;{}<>]|\/\*|\*\//;
50
+ const CSS_VALUE_URL_RE = /\burl\s*\(/i;
51
+ const CSS_VALUE_CONTROL_RE = /[\u0000-\u001f\u007f]/;
52
+ const URL_FUNCTION_RE =
53
+ /\burl\s*\(\s*(?:"([^"]*)"|'([^']*)'|([^)'"]*?))\s*\)/gi;
54
+ // The image-fill fit marker (`ImageFillControls.tsx`'s `imageFitMarker`) is
55
+ // the one recognized comment shape allowed inside a background-image
56
+ // value — every other /* ... *\/ is still rejected as a breakout risk.
57
+ const IMAGE_FIT_MARKER_RE =
58
+ /\/\*\s*agent-native-image-fit:(?:fill|fit|crop|tile)\s*\*\//gi;
59
+
60
+ /**
61
+ * CSS properties allowed to carry a `url(...)` reference (fill images).
62
+ * Deliberately excludes the `background` shorthand — only the exact
63
+ * `background-image` longhand that `imageFillToBackgroundStyles` commits is
64
+ * trusted with a sanitized url(); shorthand `background: url(...)` values
65
+ * (e.g. hand-edited in the Code panel) stay on the strict no-url path.
66
+ */
67
+ const URL_CAPABLE_PROPERTIES = new Set(["background-image"]);
68
+
69
+ /**
70
+ * Safety gate for a single `url(...)` reference inside a background-image
71
+ * value. Allows http(s), protocol-relative, root/relative paths, and
72
+ * `data:image/...` URIs — the shapes `imageFillToBackgroundStyles` and the
73
+ * fill picker actually produce. Rejects `javascript:`, non-image `data:`
74
+ * payloads (e.g. `data:text/html`), and any other unrecognized scheme.
75
+ */
76
+ export function isSafeCssUrlReference(rawUrl: string): boolean {
77
+ const url = rawUrl.trim();
78
+ if (!url) return false;
79
+ if (CSS_VALUE_CONTROL_RE.test(url)) return false;
80
+ // A legitimate URL/data-URI never needs a literal angle bracket or quote —
81
+ // reject outright rather than rely on the outer HTML/CSS writer to escape
82
+ // them correctly.
83
+ if (/[<>"']/.test(url)) return false;
84
+ if (/^javascript\s*:/i.test(url)) return false;
85
+ if (/^data\s*:/i.test(url)) return /^data:image\//i.test(url);
86
+ if (/^[a-z][a-z0-9+.-]*:/i.test(url)) {
87
+ // Any other explicit scheme (vbscript:, file:, etc.) — only the
88
+ // web-safe ones are allowed.
89
+ return /^https?:\/\//i.test(url);
90
+ }
91
+ // No scheme — protocol-relative ("//host/...") or relative/root path.
92
+ return true;
93
+ }
94
+
95
+ /**
96
+ * Reject caller-supplied CSS values before interpolation into the managed
97
+ * stylesheet. Allows useful CSS functions (`calc(...)`, `var(...)`,
98
+ * `rgb(...)`) but blocks declaration/rule breakouts and remote-resource
99
+ * hooks — same policy as the managed motion block.
100
+ *
101
+ * `url(...)` is rejected unconditionally here; callers persisting a
102
+ * sanitized fill image reference (`background-image`) should use
103
+ * {@link isSafeBreakpointCssValueForProperty} instead, which applies the
104
+ * scheme allowlist from {@link isSafeCssUrlReference} per-reference.
105
+ */
106
+ export function isSafeBreakpointCssValue(value: string): boolean {
107
+ if (typeof value !== "string") return false;
108
+ const trimmed = value.trim();
109
+ if (trimmed.length === 0) return false;
110
+ if (
111
+ CSS_VALUE_CONTROL_RE.test(value) ||
112
+ CSS_VALUE_BREAKOUT_RE.test(value) ||
113
+ CSS_VALUE_URL_RE.test(value)
114
+ ) {
115
+ return false;
116
+ }
117
+ return true;
118
+ }
119
+
120
+ /**
121
+ * Same breakout/control-character policy as {@link isSafeBreakpointCssValue},
122
+ * but for `background-image` every `url(...)` reference is checked against
123
+ * {@link isSafeCssUrlReference} (which itself rejects control characters and
124
+ * `<>"'`) instead of being rejected outright. A validated `url(...)` — a
125
+ * `data:image/...` URI legitimately contains a `;` before `base64,` — is
126
+ * excised before the generic breakout check runs, so that check only ever
127
+ * sees the CSS *around* the url() reference.
128
+ */
129
+ function isSafeBackgroundValue(value: string): boolean {
130
+ if (typeof value !== "string") return false;
131
+ if (value.trim().length === 0) return false;
132
+
133
+ URL_FUNCTION_RE.lastIndex = 0;
134
+ let match: RegExpExecArray | null;
135
+ let lastIndex = 0;
136
+ let withoutValidatedParts = "";
137
+ while ((match = URL_FUNCTION_RE.exec(value))) {
138
+ const raw = match[1] ?? match[2] ?? match[3] ?? "";
139
+ if (!isSafeCssUrlReference(raw)) return false;
140
+ withoutValidatedParts += value.slice(lastIndex, match.index);
141
+ lastIndex = URL_FUNCTION_RE.lastIndex;
142
+ }
143
+ withoutValidatedParts += value.slice(lastIndex);
144
+ // Any "url(" left over wasn't matched by the well-formed pattern above
145
+ // (malformed/unterminated) — reject rather than pass it through.
146
+ if (CSS_VALUE_URL_RE.test(withoutValidatedParts)) return false;
147
+
148
+ // Strip the one recognized comment shape (the image-fill fit marker) too,
149
+ // so a legitimate fill commit isn't rejected just for carrying it. Any
150
+ // OTHER comment still trips the breakout check below.
151
+ const withoutFitMarker = withoutValidatedParts.replace(
152
+ IMAGE_FIT_MARKER_RE,
153
+ "",
154
+ );
155
+ if (
156
+ CSS_VALUE_CONTROL_RE.test(withoutFitMarker) ||
157
+ CSS_VALUE_BREAKOUT_RE.test(withoutFitMarker)
158
+ ) {
159
+ return false;
160
+ }
161
+ return true;
162
+ }
163
+
164
+ /**
165
+ * Value safety check for a given CSS property: routes `background-image`
166
+ * through {@link isSafeBackgroundValue} (which allows sanitized `url(...)`
167
+ * references) and every other property — including the `background`
168
+ * shorthand — through the strict {@link isSafeBreakpointCssValue}.
169
+ */
170
+ export function isSafeBreakpointCssValueForProperty(
171
+ property: string,
172
+ value: string,
173
+ ): boolean {
174
+ if (URL_CAPABLE_PROPERTIES.has(property)) return isSafeBackgroundValue(value);
175
+ return isSafeBreakpointCssValue(value);
176
+ }
177
+
178
+ /** Valid (optionally vendor-prefixed) CSS property identifier. */
179
+ export function isSafeBreakpointCssProperty(property: string): boolean {
180
+ return /^-?[a-zA-Z][a-zA-Z0-9-]*$/.test(property);
181
+ }
182
+
183
+ // ─── HTML block extraction / injection ───────────────────────────────────────
184
+
185
+ const OPEN_RE = /<style\b(?=[^>]*\bdata-agent-native-breakpoints\b)[^>]*>/i;
186
+
187
+ /**
188
+ * Extract the CSS body of the managed `<style data-agent-native-breakpoints>`
189
+ * block. Returns `null` when the document has no managed block.
190
+ */
191
+ export function extractManagedBreakpointCss(html: string): string | null {
192
+ const openMatch = OPEN_RE.exec(html);
193
+ if (!openMatch) return null;
194
+ const bodyStart = openMatch.index + openMatch[0].length;
195
+ const afterOpen = html.slice(bodyStart);
196
+ const closeMatch = /<\s*\/\s*style\b[^>]*>/i.exec(afterOpen);
197
+ if (!closeMatch) return null;
198
+ return afterOpen.slice(0, closeMatch.index).trim();
199
+ }
200
+
201
+ /**
202
+ * Inject or replace the managed block. Inserts before `</head>` when no
203
+ * managed block exists, or at the top of the document when there is no
204
+ * `<head>`. Passing empty CSS removes the block entirely.
205
+ */
206
+ export function injectManagedBreakpointCss(html: string, css: string): string {
207
+ const openMatch = OPEN_RE.exec(html);
208
+ const trimmed = css.trim();
209
+ const block =
210
+ trimmed.length > 0
211
+ ? `<style data-agent-native-breakpoints>\n${trimmed}\n</style>`
212
+ : "";
213
+
214
+ if (openMatch) {
215
+ const bodyStart = openMatch.index + openMatch[0].length;
216
+ const afterOpen = html.slice(bodyStart);
217
+ const closeMatch = /<\s*\/\s*style\b[^>]*>/i.exec(afterOpen);
218
+ if (closeMatch) {
219
+ const closeEnd = bodyStart + closeMatch.index + closeMatch[0].length;
220
+ // Removing the block: also swallow one trailing newline so repeated
221
+ // add/remove cycles don't accumulate blank lines.
222
+ if (block === "") {
223
+ const after = html.slice(closeEnd);
224
+ return html.slice(0, openMatch.index) + after.replace(/^\n/, "");
225
+ }
226
+ return html.slice(0, openMatch.index) + block + html.slice(closeEnd);
227
+ }
228
+ }
229
+
230
+ if (block === "") return html;
231
+ const headClose = html.lastIndexOf("</head>");
232
+ if (headClose !== -1) {
233
+ return html.slice(0, headClose) + block + "\n" + html.slice(headClose);
234
+ }
235
+ return block + "\n" + html;
236
+ }
237
+
238
+ // ─── CSS body parse / serialize ──────────────────────────────────────────────
239
+
240
+ /**
241
+ * Parse a managed CSS body into the model. Tolerant of hand edits made in
242
+ * the Code panel: unknown at-rules and selectors are skipped, not errors.
243
+ */
244
+ export function parseBreakpointMediaCss(css: string): BreakpointMediaModel {
245
+ const model: BreakpointMediaModel = {};
246
+ const mediaRe = /@media\s*\(\s*max-width\s*:\s*(\d+(?:\.\d+)?)px\s*\)\s*\{/g;
247
+ let mediaMatch: RegExpExecArray | null;
248
+
249
+ while ((mediaMatch = mediaRe.exec(css)) !== null) {
250
+ const maxWidthPx = Math.round(Number.parseFloat(mediaMatch[1]));
251
+ if (!Number.isFinite(maxWidthPx) || maxWidthPx <= 0) continue;
252
+ const bodyStart = mediaMatch.index + mediaMatch[0].length;
253
+ const body = extractBlock(css, bodyStart);
254
+ if (body === null) continue;
255
+ mediaRe.lastIndex = bodyStart + body.length + 1;
256
+
257
+ const ruleRe =
258
+ /\[data-agent-native-node-id="((?:\\.|[^"\\])*)"\]\s*\{([^}]*)\}/g;
259
+ let ruleMatch: RegExpExecArray | null;
260
+ while ((ruleMatch = ruleRe.exec(body)) !== null) {
261
+ const nodeId = unescAttr(ruleMatch[1]);
262
+ if (!nodeId) continue;
263
+ const declarations = ruleMatch[2]
264
+ .split(";")
265
+ .map((decl) => decl.trim())
266
+ .filter(Boolean);
267
+ for (const declaration of declarations) {
268
+ const colon = declaration.indexOf(":");
269
+ if (colon <= 0) continue;
270
+ const property = declaration.slice(0, colon).trim();
271
+ const value = declaration.slice(colon + 1).trim();
272
+ if (!isSafeBreakpointCssProperty(property)) continue;
273
+ if (!isSafeBreakpointCssValueForProperty(property, value)) continue;
274
+ const bucketKey = String(maxWidthPx);
275
+ model[bucketKey] ??= {};
276
+ model[bucketKey][nodeId] ??= {};
277
+ model[bucketKey][nodeId][property] = value;
278
+ }
279
+ }
280
+ }
281
+
282
+ return model;
283
+ }
284
+
285
+ /**
286
+ * Serialize the model into a deterministic CSS body: buckets widest bound
287
+ * FIRST (narrower ranges later in source so they win the desktop-down
288
+ * cascade), node ids and properties sorted alphabetically.
289
+ */
290
+ export function serializeBreakpointMediaModel(
291
+ model: BreakpointMediaModel,
292
+ ): string {
293
+ const buckets = Object.keys(model)
294
+ .map((key) => Number.parseInt(key, 10))
295
+ .filter((width) => Number.isFinite(width) && width > 0)
296
+ .sort((a, b) => b - a);
297
+
298
+ const blocks: string[] = [];
299
+ for (const maxWidthPx of buckets) {
300
+ const nodes = model[String(maxWidthPx)];
301
+ if (!nodes) continue;
302
+ const nodeIds = Object.keys(nodes).sort((a, b) => a.localeCompare(b));
303
+ const rules: string[] = [];
304
+ for (const nodeId of nodeIds) {
305
+ const declarations = nodes[nodeId];
306
+ const properties = Object.keys(declarations).sort((a, b) =>
307
+ a.localeCompare(b),
308
+ );
309
+ if (properties.length === 0) continue;
310
+ const lines = properties.map(
311
+ (property) => ` ${property}: ${declarations[property]};`,
312
+ );
313
+ rules.push(
314
+ ` [data-agent-native-node-id="${escAttr(nodeId)}"] {\n` +
315
+ `${lines.join("\n")}\n` +
316
+ ` }`,
317
+ );
318
+ }
319
+ if (rules.length === 0) continue;
320
+ blocks.push(
321
+ `@media (max-width: ${maxWidthPx}px) {\n${rules.join("\n")}\n}`,
322
+ );
323
+ }
324
+ return blocks.join("\n\n");
325
+ }
326
+
327
+ // ─── High-level document operations ──────────────────────────────────────────
328
+
329
+ /**
330
+ * Set (or overwrite) one managed declaration for a node at a max-width
331
+ * scope, returning the updated HTML. Throws on unsafe property/value — the
332
+ * caller decides how to surface that.
333
+ */
334
+ export function setBreakpointMediaDeclaration(
335
+ html: string,
336
+ args: {
337
+ nodeId: string;
338
+ maxWidthPx: number;
339
+ property: string;
340
+ value: string;
341
+ },
342
+ ): string {
343
+ const property = normalizeCssPropertyName(args.property.trim());
344
+ if (!isSafeBreakpointCssProperty(property)) {
345
+ throw new Error(
346
+ `Invalid breakpoint override property: "${args.property}".`,
347
+ );
348
+ }
349
+ if (!isSafeBreakpointCssValueForProperty(property, args.value)) {
350
+ throw new Error(
351
+ `Invalid breakpoint override value for "${property}": semicolons, braces, comments, angle brackets, control characters, and unsafe url(...) references are not allowed.`,
352
+ );
353
+ }
354
+ if (!Number.isFinite(args.maxWidthPx) || args.maxWidthPx <= 0) {
355
+ throw new Error(`Invalid breakpoint bound: ${args.maxWidthPx}px.`);
356
+ }
357
+ const model = parseBreakpointMediaCss(
358
+ extractManagedBreakpointCss(html) ?? "",
359
+ );
360
+ const bucketKey = String(Math.round(args.maxWidthPx));
361
+ model[bucketKey] ??= {};
362
+ model[bucketKey][args.nodeId] ??= {};
363
+ model[bucketKey][args.nodeId][property] = args.value.trim();
364
+ return injectManagedBreakpointCss(html, serializeBreakpointMediaModel(model));
365
+ }
366
+
367
+ /**
368
+ * Remove one managed declaration (reset the property back to the base /
369
+ * wider-scope value). Prunes empty rules, buckets, and — when nothing is
370
+ * left — the whole managed block.
371
+ */
372
+ export function removeBreakpointMediaDeclaration(
373
+ html: string,
374
+ args: { nodeId: string; maxWidthPx: number; property: string },
375
+ ): string {
376
+ const css = extractManagedBreakpointCss(html);
377
+ if (css === null) return html;
378
+ const model = parseBreakpointMediaCss(css);
379
+ const bucketKey = String(Math.round(args.maxWidthPx));
380
+ const property = normalizeCssPropertyName(args.property.trim());
381
+ const node = model[bucketKey]?.[args.nodeId];
382
+ if (!node || !(property in node)) return html;
383
+ delete node[property];
384
+ if (Object.keys(node).length === 0) {
385
+ delete model[bucketKey][args.nodeId];
386
+ }
387
+ if (Object.keys(model[bucketKey]).length === 0) {
388
+ delete model[bucketKey];
389
+ }
390
+ return injectManagedBreakpointCss(html, serializeBreakpointMediaModel(model));
391
+ }
392
+
393
+ /**
394
+ * All managed declarations for one node (every bound), flattened and sorted
395
+ * widest bound first. Pass `null` nodeId to list every declaration.
396
+ */
397
+ export function getBreakpointMediaDeclarations(
398
+ html: string,
399
+ nodeId?: string | null,
400
+ ): BreakpointMediaDeclaration[] {
401
+ const css = extractManagedBreakpointCss(html);
402
+ if (css === null) return [];
403
+ const model = parseBreakpointMediaCss(css);
404
+ const declarations: BreakpointMediaDeclaration[] = [];
405
+ for (const bucketKey of Object.keys(model)) {
406
+ const maxWidthPx = Number.parseInt(bucketKey, 10);
407
+ for (const [ruleNodeId, props] of Object.entries(model[bucketKey])) {
408
+ if (nodeId != null && ruleNodeId !== nodeId) continue;
409
+ for (const [property, value] of Object.entries(props)) {
410
+ declarations.push({ maxWidthPx, nodeId: ruleNodeId, property, value });
411
+ }
412
+ }
413
+ }
414
+ return declarations.sort(
415
+ (a, b) =>
416
+ b.maxWidthPx - a.maxWidthPx ||
417
+ a.nodeId.localeCompare(b.nodeId) ||
418
+ a.property.localeCompare(b.property),
419
+ );
420
+ }
421
+
422
+ // ─── Override state (inspector indicator contract) ───────────────────────────
423
+
424
+ /** One breakpoint override of a property, from either persistence layer. */
425
+ export interface BreakpointPropertyOverride {
426
+ /** Inclusive upper viewport bound the override applies below. */
427
+ maxWidthPx: number;
428
+ /** Where the override persists. */
429
+ source: "class" | "media";
430
+ /** Utility token (class source) or CSS value (media source). */
431
+ value: string;
432
+ }
433
+
434
+ /** Result of {@link getBreakpointOverrideState}. */
435
+ export interface BreakpointOverrideState {
436
+ /** Every override of the property, widest bound first. */
437
+ overrides: BreakpointPropertyOverride[];
438
+ /** True when an override exists exactly at the active scope's bound. */
439
+ overriddenAtActive: boolean;
440
+ /**
441
+ * The active scope's upper bound (from `breakpointUpperBoundPx`), or null
442
+ * when the active frame is the widest context (base editing).
443
+ */
444
+ activeUpperBoundPx: number | null;
445
+ }
446
+
447
+ /**
448
+ * EditPanel contract: per-property override indicators for the active
449
+ * breakpoint. Aggregates BOTH persistence layers — max-width-scoped
450
+ * responsive classes on the element and managed `@media` rules for its
451
+ * node id.
452
+ *
453
+ * @param args.className The element's class attribute value.
454
+ * @param args.html The screen HTML (for the managed media block).
455
+ * Optional — omit to check classes only.
456
+ * @param args.nodeId The element's `data-agent-native-node-id`.
457
+ * @param args.property CSS property (camelCase or kebab-case).
458
+ * @param args.breakpointWidths Widths of the design's breakpoint frames.
459
+ * @param args.baseWidthPx The primary frame's width (base context).
460
+ * @param args.activeWidthPx The active breakpoint frame width, or null
461
+ * when editing the base.
462
+ */
463
+ export function getBreakpointOverrideState(args: {
464
+ className: string;
465
+ html?: string | null;
466
+ nodeId?: string | null;
467
+ property: string;
468
+ breakpointWidths: readonly number[];
469
+ baseWidthPx?: number | null;
470
+ activeWidthPx?: number | null;
471
+ }): BreakpointOverrideState {
472
+ const property = normalizeCssPropertyName(args.property.trim());
473
+ const stems = utilityStemsForCssProperty(property);
474
+
475
+ const overrides: BreakpointPropertyOverride[] = [];
476
+ for (const stem of stems) {
477
+ for (const override of maxWidthOverridesForStem(args.className, stem)) {
478
+ overrides.push({
479
+ maxWidthPx: override.boundPx,
480
+ source: "class",
481
+ value: override.utility,
482
+ });
483
+ }
484
+ }
485
+ if (args.html && args.nodeId) {
486
+ for (const declaration of getBreakpointMediaDeclarations(
487
+ args.html,
488
+ args.nodeId,
489
+ )) {
490
+ if (declaration.property !== property) continue;
491
+ overrides.push({
492
+ maxWidthPx: declaration.maxWidthPx,
493
+ source: "media",
494
+ value: declaration.value,
495
+ });
496
+ }
497
+ }
498
+ overrides.sort((a, b) => b.maxWidthPx - a.maxWidthPx);
499
+
500
+ const activeUpperBoundPx =
501
+ args.activeWidthPx != null
502
+ ? breakpointUpperBoundPx(
503
+ args.breakpointWidths,
504
+ args.activeWidthPx,
505
+ args.baseWidthPx,
506
+ )
507
+ : null;
508
+
509
+ return {
510
+ overrides,
511
+ overriddenAtActive:
512
+ activeUpperBoundPx !== null &&
513
+ overrides.some((override) => override.maxWidthPx === activeUpperBoundPx),
514
+ activeUpperBoundPx,
515
+ };
516
+ }
517
+
518
+ // ─── Internal helpers ────────────────────────────────────────────────────────
519
+
520
+ /** Escape a string for a CSS attribute selector value (`\` and `"`). */
521
+ function escAttr(value: string): string {
522
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
523
+ }
524
+
525
+ function unescAttr(value: string): string {
526
+ return value.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
527
+ }
528
+
529
+ /**
530
+ * Find the content of the CSS block that starts just after position `start`
531
+ * (just after the opening `{`). Returns `null` on unbalanced braces.
532
+ */
533
+ function extractBlock(css: string, start: number): string | null {
534
+ let depth = 1;
535
+ let i = start;
536
+ while (i < css.length && depth > 0) {
537
+ if (css[i] === "{") depth++;
538
+ else if (css[i] === "}") depth--;
539
+ i++;
540
+ }
541
+ if (depth !== 0) return null;
542
+ return css.slice(start, i - 1);
543
+ }