@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,548 @@
1
+ /**
2
+ * Managed element interaction-state block for the Design Studio.
3
+ *
4
+ * Element-level pseudo-class states (hover / focus / focus-visible / active /
5
+ * disabled) — NOT to be confused with `design-state.ts` / `StatesPanel`,
6
+ * which model whole-*screen* app states (Loading / Empty / Error, data
7
+ * fixtures, live captures). This module edits ONE element's interactive
8
+ * styling; that one edits which named app-level scenario the whole canvas is
9
+ * showing. Both features coexist: a screen state (e.g. "Empty") and an
10
+ * element interaction state (e.g. "Hover" on a button) are independent axes.
11
+ *
12
+ * Overrides persist into a single managed
13
+ * `<style data-agent-native-states>` block as real, plain CSS pseudo-class
14
+ * rules — readable and editable in the Code panel, and portable to any HTML
15
+ * export with zero runtime dependency:
16
+ *
17
+ * [data-agent-native-node-id="btn_1"]:hover {
18
+ * background-color: #111827;
19
+ * }
20
+ *
21
+ * Follows the managed-block conventions of `breakpoint-media.ts` /
22
+ * `motion-compiler.ts`:
23
+ * - **Deterministic**: same model → byte-identical CSS. Node ids, states, and
24
+ * properties are sorted alphabetically; states within a node id follow the
25
+ * fixed `STATE_ORDER` (hover → focus → focus-visible → active → disabled)
26
+ * so cascade order is stable and predictable when two states could both
27
+ * apply (e.g. `:hover` and `:focus` at once).
28
+ * - **Targets by node id**: rules use `[data-agent-native-node-id="<id>"]`
29
+ * selectors — no class/id coupling, consistent with breakpoint overrides
30
+ * and motion keyframes.
31
+ * - **No dependencies**, pure string transforms. SSR-safe (no DOM APIs).
32
+ *
33
+ * ─── Forced-preview mechanism (the phase-2 bridge contract) ─────────────────
34
+ *
35
+ * The canvas iframe cannot reliably fake `:hover`/`:focus`/`:active` browser
36
+ * pseudo-classes on demand (no real pointer is over the element, focus would
37
+ * steal keyboard input, `:active` requires a held mouse button). Instead,
38
+ * `duplicateStatePreviewRules(html)` derives a **parallel twin rule** for
39
+ * every managed state rule, keyed off a plain HTML attribute instead of the
40
+ * pseudo-class:
41
+ *
42
+ * [data-agent-native-node-id="btn_1"]:hover { background-color: #111827; }
43
+ * [data-agent-native-node-id="btn_1"][data-an-state-preview="hover"] {
44
+ * background-color: #111827;
45
+ * }
46
+ *
47
+ * The twin rules are appended inside the SAME managed
48
+ * `<style data-agent-native-states>` block (so exported/standalone HTML
49
+ * always carries them — no separate stylesheet, no extra bridge script).
50
+ * Forcing a live preview of element `btn_1`'s Hover state is then just:
51
+ *
52
+ * element.setAttribute("data-an-state-preview", "hover")
53
+ *
54
+ * ...and clearing it is just removing that attribute. No JS re-evaluation of
55
+ * styles, no class toggling per property, no iframe-side CSS generation —
56
+ * the bridge's entire job is setting/clearing one attribute on the selected
57
+ * element whenever the inspector's active state changes. This module owns
58
+ * generating/refreshing the twin rules whenever a state rule changes; the
59
+ * bridge and DesignEditor own driving the attribute (phase 2).
60
+ *
61
+ * ─── Transitions ─────────────────────────────────────────────────────────────
62
+ *
63
+ * `transition` (and longhand `transition-*` properties) are ordinary CSS
64
+ * properties on the BASE element, not part of any state rule — they belong
65
+ * whichever way the base element's styling is already authored (inline
66
+ * `style` attribute or a class), same as any other non-interactive property.
67
+ * This module does not special-case them: set `transition: background-color
68
+ * 150ms ease` on the base element (via the existing inline-style / EditPanel
69
+ * commit path) and the managed `:hover` rule above will animate exactly as
70
+ * authored, in both the real `:hover` path and the forced-preview twin path
71
+ * (since the twin rule only changes which selector matches — the transition
72
+ * on the base element still applies either way).
73
+ */
74
+
75
+ // ─── Model ────────────────────────────────────────────────────────────────────
76
+
77
+ /** Supported element interaction states, in fixed cascade/display order. */
78
+ export const INTERACTION_STATES = [
79
+ "hover",
80
+ "focus",
81
+ "focus-visible",
82
+ "active",
83
+ "disabled",
84
+ ] as const;
85
+
86
+ export type InteractionState = (typeof INTERACTION_STATES)[number];
87
+
88
+ const STATE_SET: ReadonlySet<string> = new Set(INTERACTION_STATES);
89
+
90
+ export function isInteractionState(
91
+ value: string | null | undefined,
92
+ ): value is InteractionState {
93
+ return typeof value === "string" && STATE_SET.has(value);
94
+ }
95
+
96
+ /**
97
+ * Parsed managed block: node id → state → CSS property → value. Plain
98
+ * nested records so callers can serialize/diff cheaply.
99
+ */
100
+ export type InteractionStatesModel = Record<
101
+ string,
102
+ Partial<Record<InteractionState, Record<string, string>>>
103
+ >;
104
+
105
+ /** One flattened managed rule, as returned by {@link listAllInteractionStateDeclarations}. */
106
+ export interface InteractionStateDeclaration {
107
+ nodeId: string;
108
+ state: InteractionState;
109
+ property: string;
110
+ value: string;
111
+ }
112
+
113
+ // ─── Validation ──────────────────────────────────────────────────────────────
114
+
115
+ const CSS_VALUE_BREAKOUT_RE = /[;{}<>]|\/\*|\*\/|\burl\s*\(/i;
116
+ const CSS_VALUE_CONTROL_RE = /[\u0000-\u001f\u007f]/;
117
+
118
+ /**
119
+ * Reject caller-supplied CSS values before interpolation into the managed
120
+ * stylesheet. Allows useful CSS functions (`calc(...)`, `var(...)`,
121
+ * `rgb(...)`) but blocks declaration/rule breakouts and remote-resource
122
+ * hooks — same policy as the managed breakpoint/motion blocks.
123
+ */
124
+ export function isSafeInteractionStateCssValue(value: string): boolean {
125
+ if (typeof value !== "string") return false;
126
+ const trimmed = value.trim();
127
+ if (trimmed.length === 0) return false;
128
+ if (CSS_VALUE_CONTROL_RE.test(value) || CSS_VALUE_BREAKOUT_RE.test(value)) {
129
+ return false;
130
+ }
131
+ return true;
132
+ }
133
+
134
+ /** Valid (optionally vendor-prefixed) CSS property identifier. */
135
+ export function isSafeInteractionStateCssProperty(property: string): boolean {
136
+ return /^-?[a-zA-Z][a-zA-Z0-9-]*$/.test(property);
137
+ }
138
+
139
+ // ─── HTML block extraction / injection ───────────────────────────────────────
140
+
141
+ const OPEN_RE = /<style\b(?=[^>]*\bdata-agent-native-states\b)[^>]*>/i;
142
+
143
+ /**
144
+ * Extract the CSS body of the managed `<style data-agent-native-states>`
145
+ * block (includes both the real pseudo-class rules and their forced-preview
146
+ * twins — both live in the same block). Returns `null` when the document has
147
+ * no managed block.
148
+ */
149
+ export function extractManagedInteractionStateCss(html: string): string | null {
150
+ const openMatch = OPEN_RE.exec(html);
151
+ if (!openMatch) return null;
152
+ const bodyStart = openMatch.index + openMatch[0].length;
153
+ const afterOpen = html.slice(bodyStart);
154
+ const closeMatch = /<\s*\/\s*style\b[^>]*>/i.exec(afterOpen);
155
+ if (!closeMatch) return null;
156
+ return afterOpen.slice(0, closeMatch.index).trim();
157
+ }
158
+
159
+ /**
160
+ * Inject or replace the managed block. Inserts before `</head>` when no
161
+ * managed block exists, or at the top of the document when there is no
162
+ * `<head>`. Passing empty CSS removes the block entirely.
163
+ */
164
+ export function injectManagedInteractionStateCss(
165
+ html: string,
166
+ css: string,
167
+ ): string {
168
+ const openMatch = OPEN_RE.exec(html);
169
+ const trimmed = css.trim();
170
+ const block =
171
+ trimmed.length > 0
172
+ ? `<style data-agent-native-states>\n${trimmed}\n</style>`
173
+ : "";
174
+
175
+ if (openMatch) {
176
+ const bodyStart = openMatch.index + openMatch[0].length;
177
+ const afterOpen = html.slice(bodyStart);
178
+ const closeMatch = /<\s*\/\s*style\b[^>]*>/i.exec(afterOpen);
179
+ if (closeMatch) {
180
+ const closeEnd = bodyStart + closeMatch.index + closeMatch[0].length;
181
+ // Removing the block: also swallow one trailing newline so repeated
182
+ // add/remove cycles don't accumulate blank lines.
183
+ if (block === "") {
184
+ const after = html.slice(closeEnd);
185
+ return html.slice(0, openMatch.index) + after.replace(/^\n/, "");
186
+ }
187
+ return html.slice(0, openMatch.index) + block + html.slice(closeEnd);
188
+ }
189
+ }
190
+
191
+ if (block === "") return html;
192
+ const headClose = html.lastIndexOf("</head>");
193
+ if (headClose !== -1) {
194
+ return html.slice(0, headClose) + block + "\n" + html.slice(headClose);
195
+ }
196
+ return block + "\n" + html;
197
+ }
198
+
199
+ // ─── CSS body parse / serialize ──────────────────────────────────────────────
200
+
201
+ /**
202
+ * Matches ONE real pseudo-class state rule for a node id, e.g.
203
+ * `[data-agent-native-node-id="btn_1"]:focus-visible { ... }`. Deliberately
204
+ * does NOT match the `[data-an-state-preview="..."]` twin rules — those are
205
+ * derived output, not source of truth, and are re-generated by
206
+ * {@link duplicateStatePreviewRules} rather than parsed back in, so hand
207
+ * edits to a twin rule in the Code panel don't fork state.
208
+ */
209
+ function stateRuleRegex(): RegExp {
210
+ return /\[data-agent-native-node-id="((?:\\.|[^"\\])*)"\]:(hover|focus-visible|focus|active|disabled)\s*\{([^}]*)\}/g;
211
+ }
212
+
213
+ /**
214
+ * Parse a managed CSS body into the model. Tolerant of hand edits made in
215
+ * the Code panel: unknown selectors (including the derived preview twins)
216
+ * and invalid declarations are skipped, not errors.
217
+ */
218
+ export function parseInteractionStatesCss(css: string): InteractionStatesModel {
219
+ const model: InteractionStatesModel = {};
220
+ const ruleRe = stateRuleRegex();
221
+ let match: RegExpExecArray | null;
222
+ while ((match = ruleRe.exec(css)) !== null) {
223
+ const nodeId = unescAttr(match[1]);
224
+ const state = match[2];
225
+ if (!nodeId || !isInteractionState(state)) continue;
226
+ const declarations = match[3]
227
+ .split(";")
228
+ .map((decl) => decl.trim())
229
+ .filter(Boolean);
230
+ for (const declaration of declarations) {
231
+ const colon = declaration.indexOf(":");
232
+ if (colon <= 0) continue;
233
+ const property = declaration.slice(0, colon).trim();
234
+ const value = declaration.slice(colon + 1).trim();
235
+ if (!isSafeInteractionStateCssProperty(property)) continue;
236
+ if (!isSafeInteractionStateCssValue(value)) continue;
237
+ model[nodeId] ??= {};
238
+ const node = model[nodeId];
239
+ node[state] ??= {};
240
+ node[state]![property] = value;
241
+ }
242
+ }
243
+ return model;
244
+ }
245
+
246
+ /**
247
+ * Serialize the real pseudo-class rules (model → CSS body), sorted node id
248
+ * then fixed state order then property, for byte-identical output. Does NOT
249
+ * include the forced-preview twins — call {@link duplicateStatePreviewRules}
250
+ * on the result (or use {@link serializeInteractionStatesModelWithPreviews})
251
+ * to append those.
252
+ */
253
+ export function serializeInteractionStatesModel(
254
+ model: InteractionStatesModel,
255
+ ): string {
256
+ const nodeIds = Object.keys(model).sort((a, b) => a.localeCompare(b));
257
+ const rules: string[] = [];
258
+ for (const nodeId of nodeIds) {
259
+ const states = model[nodeId];
260
+ for (const state of INTERACTION_STATES) {
261
+ const declarations = states[state];
262
+ if (!declarations) continue;
263
+ const properties = Object.keys(declarations).sort((a, b) =>
264
+ a.localeCompare(b),
265
+ );
266
+ if (properties.length === 0) continue;
267
+ const lines = properties.map(
268
+ (property) => ` ${property}: ${declarations[property]};`,
269
+ );
270
+ rules.push(
271
+ `[data-agent-native-node-id="${escAttr(nodeId)}"]:${state} {\n` +
272
+ `${lines.join("\n")}\n` +
273
+ `}`,
274
+ );
275
+ }
276
+ }
277
+ return rules.join("\n\n");
278
+ }
279
+
280
+ /**
281
+ * Rebuild a managed CSS body's real rules + forced-preview twins from
282
+ * scratch, given the ALREADY-PARSED model. Re-serializing the real rules
283
+ * (rather than reusing the raw input body verbatim) is what keeps this
284
+ * idempotent: a body that already contains twins from a previous run parses
285
+ * back to the same model (twins are excluded by {@link stateRuleRegex}), so
286
+ * rebuilding from that model reproduces byte-identical output instead of
287
+ * accumulating duplicate twin rules on every call.
288
+ */
289
+ function rebuildCssWithPreviews(model: InteractionStatesModel): string {
290
+ const baseBody = serializeInteractionStatesModel(model);
291
+ const previewRules: string[] = [];
292
+ const nodeIds = Object.keys(model).sort((a, b) => a.localeCompare(b));
293
+ for (const nodeId of nodeIds) {
294
+ const states = model[nodeId];
295
+ for (const state of INTERACTION_STATES) {
296
+ const declarations = states[state];
297
+ if (!declarations) continue;
298
+ const properties = Object.keys(declarations).sort((a, b) =>
299
+ a.localeCompare(b),
300
+ );
301
+ if (properties.length === 0) continue;
302
+ const lines = properties.map(
303
+ (property) => ` ${property}: ${declarations[property]};`,
304
+ );
305
+ previewRules.push(
306
+ `[data-agent-native-node-id="${escAttr(nodeId)}"][data-an-state-preview="${state}"] {\n` +
307
+ `${lines.join("\n")}\n` +
308
+ `}`,
309
+ );
310
+ }
311
+ }
312
+ if (previewRules.length === 0) return baseBody;
313
+ return baseBody === ""
314
+ ? previewRules.join("\n\n")
315
+ : `${baseBody}\n\n${previewRules.join("\n\n")}`;
316
+ }
317
+
318
+ /**
319
+ * Convenience: model → complete CSS body (real rules + forced-preview
320
+ * twins), ready to pass to {@link injectManagedInteractionStateCss}.
321
+ */
322
+ export function serializeInteractionStatesModelWithPreviews(
323
+ model: InteractionStatesModel,
324
+ ): string {
325
+ return rebuildCssWithPreviews(model);
326
+ }
327
+
328
+ /**
329
+ * Regenerate the forced-preview twin rules for a whole document from its
330
+ * current real state rules, and write the refreshed managed block back.
331
+ * Idempotent: re-running it just recomputes byte-identical twins (rebuilt
332
+ * from the parsed real-rule model, not by appending onto whatever twins were
333
+ * already present). This is the function phase-2 callers reach for after any
334
+ * state-rule mutation (or to backfill twins for a document that already has
335
+ * real rules but was authored before this mechanism existed) — it never
336
+ * touches anything outside the managed `<style data-agent-native-states>`
337
+ * block.
338
+ */
339
+ export function duplicateStatePreviewRules(html: string): string {
340
+ const css = extractManagedInteractionStateCss(html);
341
+ if (css === null) return html;
342
+ const model = parseInteractionStatesCss(css);
343
+ return injectManagedInteractionStateCss(html, rebuildCssWithPreviews(model));
344
+ }
345
+
346
+ // ─── High-level document operations ──────────────────────────────────────────
347
+
348
+ /**
349
+ * Re-derive the managed block's CSS (real rules + fresh preview twins) from
350
+ * its current real rules and write it back. Callers that mutate the model
351
+ * directly should route through this instead of hand-assembling the CSS, so
352
+ * the preview twins never drift from the real rules.
353
+ */
354
+ function writeModel(html: string, model: InteractionStatesModel): string {
355
+ return injectManagedInteractionStateCss(
356
+ html,
357
+ serializeInteractionStatesModelWithPreviews(model),
358
+ );
359
+ }
360
+
361
+ /** Read the current model out of the document (real rules only). */
362
+ function readModel(html: string): InteractionStatesModel {
363
+ return parseInteractionStatesCss(
364
+ extractManagedInteractionStateCss(html) ?? "",
365
+ );
366
+ }
367
+
368
+ /**
369
+ * List every state that has at least one declaration for `nodeId`, in fixed
370
+ * `INTERACTION_STATES` order. Empty array when the node has no overrides.
371
+ */
372
+ export function listInteractionStates(
373
+ html: string,
374
+ nodeId: string,
375
+ ): InteractionState[] {
376
+ const model = readModel(html);
377
+ const node = model[nodeId];
378
+ if (!node) return [];
379
+ return INTERACTION_STATES.filter((state) => {
380
+ const declarations = node[state];
381
+ return declarations && Object.keys(declarations).length > 0;
382
+ });
383
+ }
384
+
385
+ /**
386
+ * All declared CSS properties/values for one node's state. Returns `{}` when
387
+ * there is no managed block, node, or state entry.
388
+ */
389
+ export function readStateStyles(
390
+ html: string,
391
+ nodeId: string,
392
+ state: InteractionState,
393
+ ): Record<string, string> {
394
+ const model = readModel(html);
395
+ return { ...(model[nodeId]?.[state] ?? {}) };
396
+ }
397
+
398
+ /**
399
+ * Set (or overwrite) one managed declaration for a node's state, returning
400
+ * the updated HTML. Throws on unsafe property/value — the caller decides how
401
+ * to surface that (same contract as `setBreakpointMediaDeclaration`).
402
+ */
403
+ export function upsertStateStyle(
404
+ html: string,
405
+ nodeId: string,
406
+ state: InteractionState,
407
+ property: string,
408
+ value: string,
409
+ ): string {
410
+ return upsertStateStyles(html, nodeId, state, { [property]: value });
411
+ }
412
+
413
+ /**
414
+ * Batched form of {@link upsertStateStyle} — sets multiple declarations for
415
+ * one node's state in a single parse/serialize/inject pass. Prefer this over
416
+ * multiple sequential `upsertStateStyle` calls when committing more than one
417
+ * property at once (e.g. a shorthand expansion), matching the
418
+ * `onStylesChange` batched-commit convention used elsewhere in EditPanel.
419
+ */
420
+ export function upsertStateStyles(
421
+ html: string,
422
+ nodeId: string,
423
+ state: InteractionState,
424
+ styles: Record<string, string>,
425
+ ): string {
426
+ if (!nodeId) throw new Error("upsertStateStyles requires a nodeId.");
427
+ if (!isInteractionState(state)) {
428
+ throw new Error(`Invalid interaction state: "${state}".`);
429
+ }
430
+ const normalized: Record<string, string> = {};
431
+ for (const [rawProperty, rawValue] of Object.entries(styles)) {
432
+ const property = normalizeCssPropertyName(rawProperty.trim());
433
+ if (!isSafeInteractionStateCssProperty(property)) {
434
+ throw new Error(`Invalid interaction-state property: "${rawProperty}".`);
435
+ }
436
+ if (!isSafeInteractionStateCssValue(rawValue)) {
437
+ throw new Error(
438
+ `Invalid interaction-state value for "${property}": semicolons, braces, comments, angle brackets, control characters, and url(...) are not allowed.`,
439
+ );
440
+ }
441
+ normalized[property] = rawValue.trim();
442
+ }
443
+ const model = readModel(html);
444
+ model[nodeId] ??= {};
445
+ const node = model[nodeId];
446
+ node[state] = { ...(node[state] ?? {}), ...normalized };
447
+ return writeModel(html, model);
448
+ }
449
+
450
+ /**
451
+ * Remove one managed declaration (reset the property back to the base
452
+ * value). Prunes empty state entries, node entries, and — when nothing is
453
+ * left — the whole managed block.
454
+ */
455
+ export function removeStateProperty(
456
+ html: string,
457
+ nodeId: string,
458
+ state: InteractionState,
459
+ property: string,
460
+ ): string {
461
+ const model = readModel(html);
462
+ const node = model[nodeId];
463
+ const declarations = node?.[state];
464
+ if (!declarations) return html;
465
+ const normalizedProperty = normalizeCssPropertyName(property.trim());
466
+ if (!(normalizedProperty in declarations)) return html;
467
+ delete declarations[normalizedProperty];
468
+ if (Object.keys(declarations).length === 0) {
469
+ delete node![state];
470
+ }
471
+ if (node && Object.keys(node).length === 0) {
472
+ delete model[nodeId];
473
+ }
474
+ return writeModel(html, model);
475
+ }
476
+
477
+ /**
478
+ * Remove every managed declaration for a node's state (the state's full
479
+ * "reset" affordance — clears every overridden property at once).
480
+ */
481
+ export function clearState(
482
+ html: string,
483
+ nodeId: string,
484
+ state: InteractionState,
485
+ ): string {
486
+ const model = readModel(html);
487
+ const node = model[nodeId];
488
+ if (!node?.[state]) return html;
489
+ delete node[state];
490
+ if (Object.keys(node).length === 0) {
491
+ delete model[nodeId];
492
+ }
493
+ return writeModel(html, model);
494
+ }
495
+
496
+ /**
497
+ * All managed declarations across every node/state, flattened and sorted
498
+ * node id → fixed state order → property. Pass `nodeId` to filter to one
499
+ * node (every state), matching `getBreakpointMediaDeclarations`'s shape.
500
+ */
501
+ export function listAllInteractionStateDeclarations(
502
+ html: string,
503
+ nodeId?: string | null,
504
+ ): InteractionStateDeclaration[] {
505
+ const model = readModel(html);
506
+ const declarations: InteractionStateDeclaration[] = [];
507
+ const nodeIds = Object.keys(model).sort((a, b) => a.localeCompare(b));
508
+ for (const ruleNodeId of nodeIds) {
509
+ if (nodeId != null && ruleNodeId !== nodeId) continue;
510
+ const states = model[ruleNodeId];
511
+ for (const state of INTERACTION_STATES) {
512
+ const props = states[state];
513
+ if (!props) continue;
514
+ const properties = Object.keys(props).sort((a, b) => a.localeCompare(b));
515
+ for (const property of properties) {
516
+ declarations.push({
517
+ nodeId: ruleNodeId,
518
+ state,
519
+ property,
520
+ value: props[property],
521
+ });
522
+ }
523
+ }
524
+ }
525
+ return declarations;
526
+ }
527
+
528
+ // ─── Internal helpers ────────────────────────────────────────────────────────
529
+
530
+ /** Escape a string for a CSS attribute selector value (`\` and `"`). */
531
+ function escAttr(value: string): string {
532
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
533
+ }
534
+
535
+ function unescAttr(value: string): string {
536
+ return value.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
537
+ }
538
+
539
+ /**
540
+ * Normalize a CSS property name to kebab-case (accepts either `fontSize` or
541
+ * `font-size`). Local copy of the same normalization
542
+ * `responsive-classes.ts`/`breakpoint-media.ts` apply, kept dependency-free
543
+ * here since this module must stay a pure, standalone string transform.
544
+ */
545
+ function normalizeCssPropertyName(property: string): string {
546
+ if (property.startsWith("--")) return property;
547
+ return property.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
548
+ }