@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,116 @@
1
+ /**
2
+ * Frame-tool size presets — the equivalent of Figma's right-panel preset list
3
+ * that replaces the inspector while the Frame tool (F / A) is armed. Each
4
+ * category maps to a `framePresets.categories.<key>` i18n label; preset
5
+ * `name` values are product/device names and are intentionally left as
6
+ * untranslated literals (matching Figma's own behavior).
7
+ *
8
+ * Pure data + a couple of small pure helpers so the shape can be unit tested
9
+ * without mounting EditPanel.
10
+ *
11
+ * i18n-raw-literal-disable-file: every `name` below is a real-world
12
+ * product/device name (iPhone, Android, Instagram Post, etc.) and must stay
13
+ * untranslated in every locale, matching Figma's own preset list behavior.
14
+ */
15
+
16
+ export type FrameSizePresetCategoryKey =
17
+ | "phone"
18
+ | "tablet"
19
+ | "desktop"
20
+ | "presentation"
21
+ | "watch"
22
+ | "paper"
23
+ | "socialMedia";
24
+
25
+ export interface FrameSizePreset {
26
+ /** Product/device name — kept as a literal, never translated. */
27
+ name: string;
28
+ width: number;
29
+ height: number;
30
+ }
31
+
32
+ export interface FrameSizePresetCategory {
33
+ key: FrameSizePresetCategoryKey;
34
+ presets: FrameSizePreset[];
35
+ }
36
+
37
+ /**
38
+ * Ordered categories, first-group-expanded-by-default matching Figma (Phone
39
+ * first). Sizes are Figma's current preset list, abbreviated to the most
40
+ * common devices per category.
41
+ */
42
+ export const FRAME_SIZE_PRESET_CATEGORIES: FrameSizePresetCategory[] = [
43
+ {
44
+ key: "phone",
45
+ presets: [
46
+ { name: "iPhone 17", width: 402, height: 874 },
47
+ { name: "iPhone 16 & 17 Pro", width: 402, height: 874 },
48
+ { name: "iPhone 16", width: 393, height: 852 },
49
+ { name: "iPhone 16 & 17 Pro Max", width: 440, height: 956 },
50
+ { name: "iPhone 16 Plus", width: 430, height: 932 },
51
+ { name: "iPhone Air", width: 420, height: 912 },
52
+ { name: "iPhone 14 & 15 Pro Max", width: 430, height: 932 },
53
+ { name: "iPhone 14 & 15 Pro", width: 393, height: 852 },
54
+ { name: "iPhone 13 & 14", width: 390, height: 844 },
55
+ { name: "iPhone 14 Plus", width: 428, height: 926 },
56
+ { name: "Android Compact", width: 412, height: 917 },
57
+ { name: "Android Medium", width: 700, height: 840 },
58
+ ],
59
+ },
60
+ {
61
+ key: "tablet",
62
+ presets: [
63
+ { name: "iPad mini 8.3", width: 744, height: 1133 },
64
+ { name: "Surface Pro 8", width: 1440, height: 960 },
65
+ { name: 'iPad Pro 11"', width: 834, height: 1194 },
66
+ { name: 'iPad Pro 12.9"', width: 1024, height: 1366 },
67
+ { name: "Android Expanded", width: 1280, height: 800 },
68
+ ],
69
+ },
70
+ {
71
+ key: "desktop",
72
+ presets: [
73
+ { name: "Desktop", width: 1440, height: 1024 },
74
+ { name: "Wireframe", width: 1440, height: 1024 },
75
+ { name: "MacBook Air", width: 1280, height: 832 },
76
+ { name: 'MacBook Pro 14"', width: 1512, height: 982 },
77
+ { name: 'MacBook Pro 16"', width: 1728, height: 1117 },
78
+ { name: "TV", width: 1280, height: 720 },
79
+ ],
80
+ },
81
+ {
82
+ key: "presentation",
83
+ presets: [
84
+ { name: "Slide 16:9", width: 1920, height: 1080 },
85
+ { name: "Slide 4:3", width: 1024, height: 768 },
86
+ ],
87
+ },
88
+ {
89
+ key: "watch",
90
+ presets: [{ name: "Apple Watch 45mm", width: 198, height: 242 }],
91
+ },
92
+ {
93
+ key: "paper",
94
+ presets: [
95
+ { name: "A4", width: 595, height: 842 },
96
+ { name: "A5", width: 420, height: 595 },
97
+ { name: "Letter", width: 612, height: 792 },
98
+ { name: "Tabloid", width: 792, height: 1224 },
99
+ ],
100
+ },
101
+ {
102
+ key: "socialMedia",
103
+ presets: [
104
+ { name: "Instagram Post", width: 1080, height: 1080 },
105
+ { name: "Instagram Story", width: 1080, height: 1920 },
106
+ { name: "X Post", width: 1200, height: 675 },
107
+ { name: "Facebook Cover", width: 820, height: 312 },
108
+ { name: "LinkedIn Cover", width: 1584, height: 396 },
109
+ ],
110
+ },
111
+ ];
112
+
113
+ /** Flat list of every preset across all categories — used for lookups/tests. */
114
+ export function allFrameSizePresets(): FrameSizePreset[] {
115
+ return FRAME_SIZE_PRESET_CATEGORIES.flatMap((category) => category.presets);
116
+ }
@@ -7,6 +7,10 @@ export {
7
7
  type AlignmentVertical,
8
8
  type DistributionAxis,
9
9
  } from "./AlignmentMatrix";
10
+ export {
11
+ BreakpointOverrideIndicator,
12
+ type BreakpointOverrideIndicatorProps,
13
+ } from "./BreakpointOverrideIndicator";
10
14
  export {
11
15
  AutoLayoutMatrix,
12
16
  type AutoLayoutDirection,
@@ -38,8 +42,10 @@ export {
38
42
  type ExportSettingsValue,
39
43
  } from "./ExportSettingsPanel";
40
44
  export {
45
+ beginEyedropperPick,
41
46
  DesignColorPicker,
42
47
  endPointerGesture,
48
+ hasEyeDropperSupport,
43
49
  POINTER_GESTURE_IDLE,
44
50
  startPointerGesture,
45
51
  type DesignColorMode,
@@ -54,6 +60,13 @@ export {
54
60
  type DesignPaintType,
55
61
  type PointerGestureState,
56
62
  } from "./DesignColorPicker";
63
+ export {
64
+ allFrameSizePresets,
65
+ FRAME_SIZE_PRESET_CATEGORIES,
66
+ type FrameSizePreset,
67
+ type FrameSizePresetCategory,
68
+ type FrameSizePresetCategoryKey,
69
+ } from "./frame-size-presets";
57
70
  export {
58
71
  GradientEditor,
59
72
  defaultGradient,
@@ -73,6 +86,19 @@ export {
73
86
  type ImageFillValue,
74
87
  type ImageFitMode,
75
88
  } from "./ImageFillControls";
89
+ export {
90
+ InteractionStateOverrideIndicator,
91
+ InteractionStatePanel,
92
+ type ActiveInteractionState,
93
+ type InteractionState,
94
+ type InteractionStatePanelProps,
95
+ } from "./InteractionStatePanel";
96
+ export {
97
+ MotionKeyframeDiamond,
98
+ motionPropertyHasKeyframe,
99
+ type MotionKeyframeCssProperty,
100
+ type MotionKeyframeDiamondProps,
101
+ } from "./MotionKeyframeDiamond";
76
102
  export {
77
103
  ShaderFillsPanel,
78
104
  descriptorFromPreset,
@@ -114,6 +114,35 @@ export function normalizeScrubNumber(
114
114
  return Object.is(next, -0) ? 0 : next;
115
115
  }
116
116
 
117
+ /**
118
+ * Whether a field's unit should snap to whole numbers while the user is
119
+ * actively pointer-dragging (scrubbing) it. Px-type fields (padding, gap,
120
+ * position, size, radius, etc.) read as integers in Figma-style editors even
121
+ * though the underlying `precision` option (which also governs *typed* input
122
+ * and keyboard nudges) allows one decimal place so a typed "12.5" still
123
+ * commits legally. Scoped to "px" specifically — unitless fields like
124
+ * line-height (precision-based, fractional by design) and other units (deg,
125
+ * %) are untouched.
126
+ */
127
+ export function scrubSnapsToInteger(unit: string | undefined): boolean {
128
+ return unit === "px";
129
+ }
130
+
131
+ /**
132
+ * Rounds a live scrub-drag value to a whole number when the field's unit
133
+ * calls for integer-only scrubbing (see `scrubSnapsToInteger`), applied
134
+ * *before* `normalizeScrubNumber`'s own min/max/precision clamp so a
135
+ * following precision clamp (if any) can't reintroduce a fraction. Only the
136
+ * pointer-drag scrub gesture should call this — typed input and keyboard
137
+ * nudges keep their existing `precision`-based rounding untouched.
138
+ */
139
+ export function roundScrubDragValue(
140
+ value: number,
141
+ unit: string | undefined,
142
+ ): number {
143
+ return scrubSnapsToInteger(unit) ? Math.round(value) : value;
144
+ }
145
+
117
146
  export function formatScrubValue(
118
147
  value: number,
119
148
  options: Pick<ScrubExpressionOptions, "precision" | "unit"> = {},
@@ -0,0 +1,171 @@
1
+ import type {
2
+ DistanceGuideBand,
3
+ EqualGapGuide,
4
+ FrameBounds,
5
+ } from "@shared/canvas-math";
6
+
7
+ import type {
8
+ AlignmentGuide,
9
+ AltHoverMeasurement,
10
+ AltHoverMeasurementLine,
11
+ } from "./types";
12
+
13
+ // Snap/guide recompute runs every rAF-coalesced mousemove during a drag and
14
+ // always returns a freshly-allocated array, so referential-equality bail in
15
+ // setState never fires even when the guides are unchanged frame-to-frame
16
+ // (e.g. holding steady mid-drag, or dragging along an axis with no new
17
+ // alignment). Shallow field-compare avoids the wasted re-render (PF15).
18
+ export function alignmentGuidesEqual(a: AlignmentGuide[], b: AlignmentGuide[]) {
19
+ if (a === b) return true;
20
+ if (a.length !== b.length) return false;
21
+ for (let i = 0; i < a.length; i++) {
22
+ const x = a[i];
23
+ const y = b[i];
24
+ if (
25
+ x.orientation !== y.orientation ||
26
+ x.position !== y.position ||
27
+ x.start !== y.start ||
28
+ x.end !== y.end
29
+ ) {
30
+ return false;
31
+ }
32
+ }
33
+ return true;
34
+ }
35
+
36
+ export function distanceGuideBandEqual(
37
+ a: DistanceGuideBand,
38
+ b: DistanceGuideBand,
39
+ ): boolean {
40
+ return (
41
+ a.gapStart === b.gapStart &&
42
+ a.gapEnd === b.gapEnd &&
43
+ a.crossStart === b.crossStart &&
44
+ a.crossEnd === b.crossEnd
45
+ );
46
+ }
47
+
48
+ export function equalGapGuidesEqual(a: EqualGapGuide[], b: EqualGapGuide[]) {
49
+ if (a === b) return true;
50
+ if (a.length !== b.length) return false;
51
+ for (let i = 0; i < a.length; i++) {
52
+ const x = a[i];
53
+ const y = b[i];
54
+ if (
55
+ x.orientation !== y.orientation ||
56
+ x.gap !== y.gap ||
57
+ !distanceGuideBandEqual(x.bands[0], y.bands[0]) ||
58
+ !distanceGuideBandEqual(x.bands[1], y.bands[1])
59
+ ) {
60
+ return false;
61
+ }
62
+ }
63
+ return true;
64
+ }
65
+
66
+ export function altHoverMeasurementLineEqual(
67
+ a: AltHoverMeasurementLine | null,
68
+ b: AltHoverMeasurementLine | null,
69
+ ): boolean {
70
+ if (a === b) return true;
71
+ if (!a || !b) return false;
72
+ return (
73
+ a.orientation === b.orientation &&
74
+ a.gap === b.gap &&
75
+ a.start === b.start &&
76
+ a.end === b.end &&
77
+ a.crossPosition === b.crossPosition &&
78
+ a.overlaps === b.overlaps
79
+ );
80
+ }
81
+
82
+ export function altHoverMeasurementEqual(
83
+ a: AltHoverMeasurement | null,
84
+ b: AltHoverMeasurement | null,
85
+ ): boolean {
86
+ if (a === b) return true;
87
+ if (!a || !b) return false;
88
+ return (
89
+ altHoverMeasurementLineEqual(a.horizontal, b.horizontal) &&
90
+ altHoverMeasurementLineEqual(a.vertical, b.vertical)
91
+ );
92
+ }
93
+
94
+ /** Computes the Figma-style alt-hover measurement lines between a selection's
95
+ * bounding box and a hovered object's bounding box: one horizontal gap (x
96
+ * axis) and one vertical gap (y axis), each with the px distance and the
97
+ * line's placement. Pure/testable — no DOM, no React state. Returns null for
98
+ * an axis where the two boxes overlap (nothing meaningful to measure there),
99
+ * matching Figma's behavior of only showing the line(s) that represent real
100
+ * empty space. */
101
+ export function computeAltHoverMeasurement(
102
+ selectionBounds: FrameBounds,
103
+ hoveredBounds: FrameBounds,
104
+ ): AltHoverMeasurement {
105
+ const horizontalOverlap =
106
+ selectionBounds.top < hoveredBounds.bottom &&
107
+ selectionBounds.bottom > hoveredBounds.top;
108
+ const verticalOverlap =
109
+ selectionBounds.left < hoveredBounds.right &&
110
+ selectionBounds.right > hoveredBounds.left;
111
+
112
+ let horizontal: AltHoverMeasurementLine | null = null;
113
+ if (selectionBounds.right <= hoveredBounds.left) {
114
+ horizontal = {
115
+ orientation: "horizontal",
116
+ gap: hoveredBounds.left - selectionBounds.right,
117
+ start: selectionBounds.right,
118
+ end: hoveredBounds.left,
119
+ crossPosition: horizontalOverlap
120
+ ? (Math.max(selectionBounds.top, hoveredBounds.top) +
121
+ Math.min(selectionBounds.bottom, hoveredBounds.bottom)) /
122
+ 2
123
+ : (selectionBounds.centerY + hoveredBounds.centerY) / 2,
124
+ overlaps: false,
125
+ };
126
+ } else if (hoveredBounds.right <= selectionBounds.left) {
127
+ horizontal = {
128
+ orientation: "horizontal",
129
+ gap: selectionBounds.left - hoveredBounds.right,
130
+ start: hoveredBounds.right,
131
+ end: selectionBounds.left,
132
+ crossPosition: horizontalOverlap
133
+ ? (Math.max(selectionBounds.top, hoveredBounds.top) +
134
+ Math.min(selectionBounds.bottom, hoveredBounds.bottom)) /
135
+ 2
136
+ : (selectionBounds.centerY + hoveredBounds.centerY) / 2,
137
+ overlaps: false,
138
+ };
139
+ }
140
+
141
+ let vertical: AltHoverMeasurementLine | null = null;
142
+ if (selectionBounds.bottom <= hoveredBounds.top) {
143
+ vertical = {
144
+ orientation: "vertical",
145
+ gap: hoveredBounds.top - selectionBounds.bottom,
146
+ start: selectionBounds.bottom,
147
+ end: hoveredBounds.top,
148
+ crossPosition: verticalOverlap
149
+ ? (Math.max(selectionBounds.left, hoveredBounds.left) +
150
+ Math.min(selectionBounds.right, hoveredBounds.right)) /
151
+ 2
152
+ : (selectionBounds.centerX + hoveredBounds.centerX) / 2,
153
+ overlaps: false,
154
+ };
155
+ } else if (hoveredBounds.bottom <= selectionBounds.top) {
156
+ vertical = {
157
+ orientation: "vertical",
158
+ gap: selectionBounds.top - hoveredBounds.bottom,
159
+ start: hoveredBounds.bottom,
160
+ end: selectionBounds.top,
161
+ crossPosition: verticalOverlap
162
+ ? (Math.max(selectionBounds.left, hoveredBounds.left) +
163
+ Math.min(selectionBounds.right, hoveredBounds.right)) /
164
+ 2
165
+ : (selectionBounds.centerX + hoveredBounds.centerX) / 2,
166
+ overlaps: false,
167
+ };
168
+ }
169
+
170
+ return { horizontal, vertical };
171
+ }
@@ -0,0 +1,220 @@
1
+ export function hasBoardSurfaceContent(html: string | undefined) {
2
+ if (!html) return false;
3
+ const bodyMatch = html.match(/<body\b[^>]*>([\s\S]*?)<\/body>/i);
4
+ const content = bodyMatch?.[1] ?? html;
5
+ return content.replace(/<!--[\s\S]*?-->/g, "").trim().length > 0;
6
+ }
7
+
8
+ const BOARD_SURFACE_RENDER_STYLE = `<style data-agent-native-board-surface-render>html,body{background:transparent!important;background-color:transparent!important;background-image:none!important;}body{margin:0!important;position:relative;overflow:visible;}body>:not([data-agent-native-node-id]):not(style):not(script),body>[data-agent-native-node-id]:not([data-an-primitive]):not([data-agent-native-preserve-styles="true"]):has([data-agent-native-node-id]),body>[data-agent-native-node-id="body"],body>[data-agent-native-node-id="Body"],body>[data-agent-native-layer-name="body"],body>[data-agent-native-layer-name="Body"],body>[data-agent-native-layer-name="<body>"]{background:transparent!important;background-color:transparent!important;background-image:none!important;box-shadow:none!important;}[data-agent-native-board-backdrop-candidate="true"]{display:none!important;pointer-events:none!important;}</style>`;
9
+ export const BOARD_SURFACE_BACKGROUND = "hsl(0 0% 10%)";
10
+
11
+ const BOARD_SURFACE_BACKDROP_MIN_EDGE_PX = 2400;
12
+ const BOARD_SURFACE_BACKDROP_MIN_AREA_PX = 8_000_000;
13
+ const HTML_VOID_TAGS = new Set([
14
+ "area",
15
+ "base",
16
+ "br",
17
+ "col",
18
+ "embed",
19
+ "hr",
20
+ "img",
21
+ "input",
22
+ "link",
23
+ "meta",
24
+ "param",
25
+ "source",
26
+ "track",
27
+ "wbr",
28
+ ]);
29
+ const HTML_TAG_RE = /<!--[\s\S]*?-->|<\/?([a-zA-Z][\w:-]*)([^<>]*?)\/?>/g;
30
+
31
+ function getHtmlAttributeValue(tag: string, name: string) {
32
+ const match = tag.match(
33
+ new RegExp(
34
+ `\\s${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'=<>]+))`,
35
+ "i",
36
+ ),
37
+ );
38
+ return match?.[1] ?? match?.[2] ?? match?.[3] ?? "";
39
+ }
40
+
41
+ function getCssDeclarationValue(style: string, name: string) {
42
+ const match = style.match(
43
+ new RegExp(`(?:^|;)\\s*${name}\\s*:\\s*([^;]+)`, "i"),
44
+ );
45
+ return match?.[1]?.trim() ?? "";
46
+ }
47
+
48
+ function getCssPixelValue(style: string, name: string) {
49
+ const value = getCssDeclarationValue(style, name);
50
+ const match = value.match(/^(-?\d+(?:\.\d+)?)px$/i);
51
+ if (!match?.[1]) return null;
52
+ const parsed = Number(match[1]);
53
+ return Number.isFinite(parsed) ? parsed : null;
54
+ }
55
+
56
+ function parseCssColor(value: string) {
57
+ const trimmed = value.trim().toLowerCase();
58
+ if (!trimmed || trimmed === "transparent") return null;
59
+ const rgb = trimmed.match(
60
+ /^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)(?:\s*,\s*(\d+(?:\.\d+)?))?\s*\)$/,
61
+ );
62
+ if (rgb?.[1] && rgb[2] && rgb[3]) {
63
+ const alpha = rgb[4] === undefined ? 1 : Number(rgb[4]);
64
+ if (alpha <= 0) return null;
65
+ return [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])] as const;
66
+ }
67
+ const hex = trimmed.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
68
+ if (!hex?.[1]) return null;
69
+ const raw = hex[1];
70
+ const full =
71
+ raw.length === 3
72
+ ? raw
73
+ .split("")
74
+ .map((part) => part + part)
75
+ .join("")
76
+ : raw;
77
+ return [
78
+ Number.parseInt(full.slice(0, 2), 16),
79
+ Number.parseInt(full.slice(2, 4), 16),
80
+ Number.parseInt(full.slice(4, 6), 16),
81
+ ] as const;
82
+ }
83
+
84
+ function isNeutralBackdropColor(value: string) {
85
+ const color = parseCssColor(value);
86
+ if (!color) return false;
87
+ const max = Math.max(...color);
88
+ const min = Math.min(...color);
89
+ return min >= 180 && max - min <= 24;
90
+ }
91
+
92
+ function isAccidentalBoardBackdropTag(tag: string) {
93
+ if (
94
+ getHtmlAttributeValue(tag, "data-agent-native-board-backdrop-candidate")
95
+ ) {
96
+ return false;
97
+ }
98
+ const primitive = getHtmlAttributeValue(
99
+ tag,
100
+ "data-an-primitive",
101
+ ).toLowerCase();
102
+ if (primitive !== "rectangle" && primitive !== "rect") return false;
103
+ const style = getHtmlAttributeValue(tag, "style");
104
+ if (!style) return false;
105
+ const width = getCssPixelValue(style, "width");
106
+ const height = getCssPixelValue(style, "height");
107
+ if (width === null || height === null) return false;
108
+ if (
109
+ width < BOARD_SURFACE_BACKDROP_MIN_EDGE_PX ||
110
+ height < BOARD_SURFACE_BACKDROP_MIN_EDGE_PX ||
111
+ width * height < BOARD_SURFACE_BACKDROP_MIN_AREA_PX
112
+ ) {
113
+ return false;
114
+ }
115
+ const background =
116
+ getCssDeclarationValue(style, "background-color") ||
117
+ getCssDeclarationValue(style, "background");
118
+ return isNeutralBackdropColor(background);
119
+ }
120
+
121
+ function markAccidentalBoardBackdropCandidates(html: string) {
122
+ return html.replace(HTML_TAG_RE, (tag: string, tagName?: string) => {
123
+ if (!tagName || tag.startsWith("</")) return tag;
124
+ if (!isAccidentalBoardBackdropTag(tag)) return tag;
125
+ return tag.replace(
126
+ /\/?>$/,
127
+ (ending) => ` data-agent-native-board-backdrop-candidate="true"${ending}`,
128
+ );
129
+ });
130
+ }
131
+
132
+ function findLastHtmlStackTagIndex(
133
+ stack: Array<{ tagName: string; nodeId: string }>,
134
+ tagName: string,
135
+ ) {
136
+ for (let i = stack.length - 1; i >= 0; i--) {
137
+ if (stack[i]?.tagName === tagName) return i;
138
+ }
139
+ return -1;
140
+ }
141
+
142
+ function getCurrentLayerParentNodeId(
143
+ stack: Array<{ tagName: string; nodeId: string }>,
144
+ ) {
145
+ for (let i = stack.length - 1; i >= 0; i--) {
146
+ const nodeId = stack[i]?.nodeId;
147
+ if (nodeId) return nodeId;
148
+ }
149
+ return "body";
150
+ }
151
+
152
+ export function getBoardSurfaceRenderContent(html: string) {
153
+ if (!html) return html;
154
+ const renderHtml = markAccidentalBoardBackdropCandidates(html);
155
+ if (renderHtml.includes("data-agent-native-board-surface-render")) {
156
+ return renderHtml;
157
+ }
158
+ if (/<\/head>/i.test(html)) {
159
+ return renderHtml.replace(
160
+ /<\/head>/i,
161
+ `${BOARD_SURFACE_RENDER_STYLE}</head>`,
162
+ );
163
+ }
164
+ if (/<body\b/i.test(html)) {
165
+ return renderHtml.replace(/<body\b/i, `${BOARD_SURFACE_RENDER_STYLE}<body`);
166
+ }
167
+ return `${BOARD_SURFACE_RENDER_STYLE}${renderHtml}`;
168
+ }
169
+
170
+ /** Simple djb2-xor string hash, used to build cheap cache keys elsewhere in
171
+ * the multi-screen canvas (board content signatures, primitive-parse cache
172
+ * keys, etc). */
173
+ export function hashString(s: string): string {
174
+ let h = 5381;
175
+ for (let i = 0; i < s.length; i++) {
176
+ // h = h * 33 ^ charCode (djb2 xor variant)
177
+ h = ((h << 5) + h) ^ s.charCodeAt(i);
178
+ h = h >>> 0; // keep as unsigned 32-bit
179
+ }
180
+ return h.toString(16);
181
+ }
182
+
183
+ export function getBoardContentLayerSignature(html: string) {
184
+ const layers: string[] = [];
185
+ const stack: Array<{ tagName: string; nodeId: string }> = [];
186
+ const childCountsByParent = new Map<string, number>();
187
+
188
+ for (const match of html.matchAll(HTML_TAG_RE)) {
189
+ const token = match[0];
190
+ const tagName = match[1]?.toLowerCase();
191
+ if (!tagName) continue;
192
+
193
+ if (token.startsWith("</")) {
194
+ const index = findLastHtmlStackTagIndex(stack, tagName);
195
+ if (index >= 0) stack.splice(index);
196
+ continue;
197
+ }
198
+
199
+ const nodeId = getHtmlAttributeValue(token, "data-agent-native-node-id");
200
+ if (nodeId) {
201
+ const parentNodeId = getCurrentLayerParentNodeId(stack);
202
+ const childIndex = childCountsByParent.get(parentNodeId) ?? 0;
203
+ childCountsByParent.set(parentNodeId, childIndex + 1);
204
+ layers.push(`${nodeId}<${parentNodeId}#${childIndex}`);
205
+ }
206
+
207
+ const selfClosing = token.endsWith("/>") || HTML_VOID_TAGS.has(tagName);
208
+ if (!selfClosing) stack.push({ tagName, nodeId });
209
+ }
210
+
211
+ return `${layers.length}:${hashString(layers.join("\n"))}`;
212
+ }
213
+
214
+ export function getBoardContentKey(args: {
215
+ boardFileId: string;
216
+ boardFileContent: string;
217
+ boardIsActive: boolean;
218
+ }) {
219
+ return `${args.boardFileId}:surface`;
220
+ }
@@ -0,0 +1,35 @@
1
+ // During a zoom gesture the constant-size selection chrome is frozen (we don't
2
+ // re-render); on commit it recomputes to its fixed screen size. These transitions
3
+ // are enabled only for that brief settle, so normal selection, resize, and
4
+ // screen-switch geometry stays pinned to the frame.
5
+ export const CHROME_SETTLE_MS = 150;
6
+ const CHROME_OPACITY_TRANSITION = "opacity 150ms ease-out";
7
+ const CHROME_BORDER_SETTLE_TRANSITION = `inset ${CHROME_SETTLE_MS}ms ease-out, border-width ${CHROME_SETTLE_MS}ms ease-out, border-radius ${CHROME_SETTLE_MS}ms ease-out, ${CHROME_OPACITY_TRANSITION}`;
8
+ const SELECTION_BOX_SETTLE_TRANSITION = `border-width ${CHROME_SETTLE_MS}ms ease-out, border-radius ${CHROME_SETTLE_MS}ms ease-out, ${CHROME_OPACITY_TRANSITION}`;
9
+ const CHROME_HANDLE_SETTLE_TRANSITION = `width ${CHROME_SETTLE_MS}ms ease-out, height ${CHROME_SETTLE_MS}ms ease-out, border-width ${CHROME_SETTLE_MS}ms ease-out, top ${CHROME_SETTLE_MS}ms ease-out, bottom ${CHROME_SETTLE_MS}ms ease-out, left ${CHROME_SETTLE_MS}ms ease-out, right ${CHROME_SETTLE_MS}ms ease-out, ${CHROME_OPACITY_TRANSITION}`;
10
+ // Frame header (name + "Full view" button) is counter-scaled via
11
+ // transform to stay a fixed screen size; ease that scale on zoom-settle. opacity
12
+ // is included so the button's hover-fade (transition-opacity) keeps working.
13
+ const CHROME_LABEL_SETTLE_TRANSITION = `transform ${CHROME_SETTLE_MS}ms ease-out, ${CHROME_OPACITY_TRANSITION}`;
14
+
15
+ export function getChromeBorderTransition(chromeSettling: boolean) {
16
+ return chromeSettling
17
+ ? CHROME_BORDER_SETTLE_TRANSITION
18
+ : CHROME_OPACITY_TRANSITION;
19
+ }
20
+
21
+ export function getSelectionBoxTransition(chromeSettling: boolean) {
22
+ return chromeSettling ? SELECTION_BOX_SETTLE_TRANSITION : "none";
23
+ }
24
+
25
+ export function getChromeHandleTransition(chromeSettling: boolean) {
26
+ return chromeSettling
27
+ ? CHROME_HANDLE_SETTLE_TRANSITION
28
+ : CHROME_OPACITY_TRANSITION;
29
+ }
30
+
31
+ export function getChromeLabelTransition(chromeSettling: boolean) {
32
+ return chromeSettling
33
+ ? CHROME_LABEL_SETTLE_TRANSITION
34
+ : CHROME_OPACITY_TRANSITION;
35
+ }