@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,134 @@
1
+ import type { CSSProperties } from "react";
2
+
3
+ import { SURFACE_PADDING } from "../MultiScreenCanvas";
4
+ import type { PortableStyleSnapshot } from "../types";
5
+ import type {
6
+ CrossScreenDropAxis,
7
+ CrossScreenDropGuide,
8
+ CrossScreenDropMode,
9
+ CrossScreenDropPlacement,
10
+ CrossScreenHitTestAnchorRect,
11
+ CrossScreenHitTestResult,
12
+ FrameGeometry,
13
+ Point,
14
+ } from "./types";
15
+
16
+ export function isFinitePoint(value: unknown): value is Point {
17
+ if (!value || typeof value !== "object") return false;
18
+ const point = value as Record<string, unknown>;
19
+ return Number.isFinite(point.x) && Number.isFinite(point.y);
20
+ }
21
+
22
+ export function isPortableStyleSnapshot(
23
+ value: unknown,
24
+ ): value is PortableStyleSnapshot {
25
+ if (!value || typeof value !== "object") return false;
26
+ const snapshot = value as Record<string, unknown>;
27
+ return snapshot.version === 1 && Array.isArray(snapshot.nodes);
28
+ }
29
+
30
+ export function isCrossScreenDropPlacement(
31
+ value: unknown,
32
+ ): value is CrossScreenDropPlacement {
33
+ return value === "before" || value === "after" || value === "inside";
34
+ }
35
+
36
+ export function isCrossScreenDropAxis(
37
+ value: unknown,
38
+ ): value is CrossScreenDropAxis {
39
+ return value === "x" || value === "y";
40
+ }
41
+
42
+ export function isCrossScreenDropMode(
43
+ value: unknown,
44
+ ): value is CrossScreenDropMode {
45
+ return value === "flow-insert" || value === "absolute-container";
46
+ }
47
+
48
+ export function isCrossScreenHitTestAnchorRect(
49
+ value: unknown,
50
+ ): value is CrossScreenHitTestAnchorRect {
51
+ if (!value || typeof value !== "object") return false;
52
+ const rect = value as Record<string, unknown>;
53
+ return (
54
+ Number.isFinite(rect.left) &&
55
+ Number.isFinite(rect.top) &&
56
+ Number.isFinite(rect.width) &&
57
+ Number.isFinite(rect.height)
58
+ );
59
+ }
60
+
61
+ export function getCrossScreenDropGuideForHitTest(args: {
62
+ hit: CrossScreenHitTestResult;
63
+ targetGeometry: FrameGeometry;
64
+ targetMetadata: { width: number; height: number };
65
+ }): CrossScreenDropGuide | null {
66
+ const rect = args.hit.anchorRect;
67
+ if (!rect) return null;
68
+ const placement = args.hit.placement ?? "inside";
69
+ const axis = args.hit.axis ?? "y";
70
+ const scaleX =
71
+ args.targetGeometry.width / Math.max(1, args.targetMetadata.width);
72
+ const scaleY =
73
+ args.targetGeometry.height / Math.max(1, args.targetMetadata.height);
74
+ return {
75
+ placement,
76
+ axis,
77
+ boardRect: {
78
+ x: args.targetGeometry.x + rect.left * scaleX,
79
+ y: args.targetGeometry.y + rect.top * scaleY,
80
+ width: Math.max(1, rect.width * scaleX),
81
+ height: Math.max(1, rect.height * scaleY),
82
+ },
83
+ };
84
+ }
85
+
86
+ export function getCrossScreenDropGuideStyle(args: {
87
+ guide: CrossScreenDropGuide;
88
+ pan: Point;
89
+ scale: number;
90
+ }): CSSProperties {
91
+ const { boardRect, placement, axis } = args.guide;
92
+ const left = args.pan.x + (SURFACE_PADDING + boardRect.x) * args.scale;
93
+ const top = args.pan.y + (SURFACE_PADDING + boardRect.y) * args.scale;
94
+ const width = Math.max(1, boardRect.width * args.scale);
95
+ const height = Math.max(1, boardRect.height * args.scale);
96
+
97
+ if (placement === "inside") {
98
+ return {
99
+ left,
100
+ top,
101
+ width,
102
+ height,
103
+ border: "2px solid var(--design-editor-accent-color)",
104
+ background:
105
+ "color-mix(in srgb, var(--design-editor-accent-color) 14%, transparent)",
106
+ borderRadius: 2,
107
+ boxShadow: "none",
108
+ };
109
+ }
110
+
111
+ if (axis === "x") {
112
+ const x = placement === "before" ? left : left + width;
113
+ return {
114
+ left: x - 1,
115
+ top,
116
+ width: 2,
117
+ height: Math.max(8, height),
118
+ background: "var(--design-editor-accent-color)",
119
+ borderRadius: 999,
120
+ boxShadow: "0 0 0 1px var(--design-editor-accent-color)",
121
+ };
122
+ }
123
+
124
+ const y = placement === "before" ? top : top + height;
125
+ return {
126
+ left,
127
+ top: y - 1,
128
+ width: Math.max(8, width),
129
+ height: 2,
130
+ background: "var(--design-editor-accent-color)",
131
+ borderRadius: 999,
132
+ boxShadow: "0 0 0 1px var(--design-editor-accent-color)",
133
+ };
134
+ }
@@ -0,0 +1,185 @@
1
+ import { getRotatedFrameAABB } from "@shared/canvas-math";
2
+
3
+ import { SURFACE_PADDING } from "../MultiScreenCanvas";
4
+ import type { FrameGeometry, Point } from "./types";
5
+
6
+ // ── Overview viewport culling (PF22) ────────────────────────────────────────
7
+ //
8
+ // Boards with 100+ screens used to render every screen as a full live iframe
9
+ // regardless of whether it was anywhere near the visible viewport. This is a
10
+ // deliberately conservative culling scheme:
11
+ //
12
+ // - Visibility is computed from the *committed* pan/zoom React state (`pan`,
13
+ // `canvasZoom`), never from the imperative per-gesture-frame transform
14
+ // (zoomRef/panRef, mutated by applyViewToDom every wheel/pinch tick — see
15
+ // its comment). Recomputing this per gesture frame would mean re-rendering
16
+ // React during a gesture, exactly what applyViewToDom/scheduleViewCommit
17
+ // were built to avoid.
18
+ // - A generous overscan margin keeps screens "live" well before they
19
+ // physically enter the viewport, so a settled-but-about-to-pan-into-view
20
+ // screen (the debounced commit lags real cursor position by up to
21
+ // ~120ms — see scheduleViewCommit) is already mounted by the time it's
22
+ // reachable.
23
+ // - Two tiers, see computeScreenCullTier: a screen that has never intersected
24
+ // the overscanned viewport this session renders a lightweight placeholder;
25
+ // once visible it mounts real content and never goes back to a placeholder
26
+ // — it only ever gets hidden (Tier B), never unmounted, since iframes lose
27
+ // all internal state (scroll position, form input, Alpine/JS state) on
28
+ // unmount.
29
+
30
+ /** Escape hatch: flip to `false` to fully disable culling in one line if a
31
+ * regression appears — every screen goes back to always rendering full
32
+ * content, matching pre-culling behavior exactly. */
33
+ export const OVERVIEW_CULLING_ENABLED = true;
34
+
35
+ /** How many viewport widths/heights of margin to add around the visible
36
+ * surface, in each direction, before a screen counts as "culled". Generous
37
+ * on purpose: the mission calls for >=1.5x, so screens scrolling into view
38
+ * during an in-flight gesture are already live before the debounced
39
+ * ~120ms view-commit (see scheduleViewCommit) catches up and this
40
+ * recomputes. */
41
+ export const OVERVIEW_CULLING_OVERSCAN_FACTOR = 1.5;
42
+
43
+ export type ScreenCullTier =
44
+ /** Full content (iframe/DesignCanvas) is mounted and rendered normally. */
45
+ | "visible"
46
+ /** Has been visible before this session; content stays mounted (iframes
47
+ * never unmount — see the module doc above) but is skipped from paint via
48
+ * visibility/content-visibility, not display:none or will-change. */
49
+ | "culled"
50
+ /** Has never been visible this session; renders a lightweight placeholder
51
+ * with no iframe/content node at all. */
52
+ | "placeholder";
53
+
54
+ /** The world-space (canvas-space) rectangle currently visible inside the
55
+ * pannable surface, expanded by `overscanFactor` viewport-widths/heights in
56
+ * every direction. Built from the *committed* pan/zoom state, matching the
57
+ * same `translate(pan) scale(zoom/100)` transform applyViewToDom applies to
58
+ * the world layer — see getOverscannedViewportCanvasBounds's callers. */
59
+ export interface OverscannedViewportBounds {
60
+ left: number;
61
+ top: number;
62
+ right: number;
63
+ bottom: number;
64
+ }
65
+
66
+ /** Computes the overscanned world-space viewport rect for culling purposes.
67
+ * `surfaceSize` is the pannable surface's own on-screen size (the
68
+ * `surfaceRef` element's content box, in screen px); `pan`/`zoomPercent` are
69
+ * the committed (not per-gesture-frame) pan/zoom values. Screens are placed
70
+ * in world space with `SURFACE_PADDING` added to their raw x/y (see Screen's
71
+ * wrapper style), so this returns bounds already in that same
72
+ * `SURFACE_PADDING`-relative space — compare directly against
73
+ * `geometry.x`/`geometry.y`-based frame bounds, no further offset needed.
74
+ * Returns `null` when the surface has no measured size yet (e.g. before the
75
+ * first layout pass) — callers should treat that as "cannot determine
76
+ * visibility yet" and fall back to treating everything as visible. */
77
+ export function getOverscannedViewportCanvasBounds(
78
+ surfaceSize: { width: number; height: number },
79
+ pan: Point,
80
+ zoomPercent: number,
81
+ overscanFactor: number = OVERVIEW_CULLING_OVERSCAN_FACTOR,
82
+ ): OverscannedViewportBounds | null {
83
+ if (surfaceSize.width <= 0 || surfaceSize.height <= 0) return null;
84
+ const scale = zoomPercent / 100;
85
+ if (!(scale > 0)) return null;
86
+ // Visible world-space rect: screen-space [0, surfaceSize] maps back through
87
+ // the world transform (`screenPoint = pan + worldPoint * scale`) to
88
+ // `worldPoint = (screenPoint - pan) / scale`. This mirrors
89
+ // screenToCanvasPoint's own inverse-transform math but is kept local here
90
+ // (rather than imported) since it operates on the *committed* pan/zoom
91
+ // React state specifically, not the live gesture pan/zoom.
92
+ const visibleLeft = -pan.x / scale;
93
+ const visibleTop = -pan.y / scale;
94
+ const visibleWidth = surfaceSize.width / scale;
95
+ const visibleHeight = surfaceSize.height / scale;
96
+ const overscanX = visibleWidth * overscanFactor;
97
+ const overscanY = visibleHeight * overscanFactor;
98
+ return {
99
+ left: visibleLeft - overscanX - SURFACE_PADDING,
100
+ top: visibleTop - overscanY - SURFACE_PADDING,
101
+ right: visibleLeft + visibleWidth + overscanX - SURFACE_PADDING,
102
+ bottom: visibleTop + visibleHeight + overscanY - SURFACE_PADDING,
103
+ };
104
+ }
105
+
106
+ /** True when `geometry`'s (rotation-aware) bounds intersect the overscanned
107
+ * viewport rect at all — i.e. the screen is not fully outside it. Uses
108
+ * `getRotatedFrameAABB` so a rotated frame's actual on-screen footprint is
109
+ * tested, not its unrotated local rect. */
110
+ export function isFrameWithinOverscannedViewport(
111
+ geometry: FrameGeometry,
112
+ viewport: OverscannedViewportBounds,
113
+ ): boolean {
114
+ const bounds = getRotatedFrameAABB(geometry);
115
+ return (
116
+ bounds.right >= viewport.left &&
117
+ bounds.left <= viewport.right &&
118
+ bounds.bottom >= viewport.top &&
119
+ bounds.top <= viewport.bottom
120
+ );
121
+ }
122
+
123
+ /**
124
+ * Item 4 — frame-tool/preset new-screen placement guard. A degenerate camera
125
+ * (corrupted/extreme pan+zoom — see item 5's camera-restore fix) makes
126
+ * getCanvasPoint's screen-to-world conversion blow up (dividing by a near-
127
+ * zero zoom scale), so a click-to-place or drag-to-draw frame gesture can
128
+ * compute world coordinates in the tens of thousands (observed: ±65536-ish)
129
+ * instead of landing near what the user actually clicked. Clamps the
130
+ * proposed geometry's origin to sit within `viewport` (the current
131
+ * OverscannedViewportBounds with overscanFactor 0, i.e. the exact visible
132
+ * world-rect) — centering it there when the proposed origin falls outside —
133
+ * so a bad camera can never fling a new screen to infinity. Only the origin
134
+ * is clamped (not width/height): the frame tool's own min/default size rules
135
+ * already bound those, and centering a same-sized frame preserves the
136
+ * gesture's intended dimensions.
137
+ */
138
+ export function clampFrameGeometryToViewport(
139
+ geometry: FrameGeometry,
140
+ viewport: OverscannedViewportBounds | null,
141
+ ): FrameGeometry {
142
+ if (!viewport) return geometry;
143
+ const viewportWidth = viewport.right - viewport.left;
144
+ const viewportHeight = viewport.bottom - viewport.top;
145
+ if (!(viewportWidth > 0) || !(viewportHeight > 0)) return geometry;
146
+ const isWithin = isFrameWithinOverscannedViewport(geometry, viewport);
147
+ if (isWithin) return geometry;
148
+ return {
149
+ ...geometry,
150
+ x: viewport.left + (viewportWidth - geometry.width) / 2,
151
+ y: viewport.top + (viewportHeight - geometry.height) / 2,
152
+ };
153
+ }
154
+
155
+ /** Resolves the culling tier for a single screen. `alwaysVisible` covers the
156
+ * mission's "always treated as visible" overrides: the active/board screen
157
+ * and anything in the current selection, regardless of position — Figma
158
+ * itself never culls the object you're actively editing or have selected,
159
+ * and keeping these paths iframe-backed avoids any risk of interrupting
160
+ * in-progress edits/bridge state on the screen the user is looking at.
161
+ * `viewport` is `null` when surface size isn't known yet (initial layout) —
162
+ * treated as visible so nothing is incorrectly culled before we can measure.
163
+ * `hasBeenVisible` should reflect whether this screen id has *ever* been
164
+ * visible this session (see the hasBeenVisibleRef Set in MultiScreenCanvas):
165
+ * once true, a screen can only be "visible" or "culled", never regress to
166
+ * "placeholder". */
167
+ export function computeScreenCullTier({
168
+ geometry,
169
+ viewport,
170
+ alwaysVisible,
171
+ hasBeenVisible,
172
+ }: {
173
+ geometry: FrameGeometry;
174
+ viewport: OverscannedViewportBounds | null;
175
+ alwaysVisible: boolean;
176
+ hasBeenVisible: boolean;
177
+ }): ScreenCullTier {
178
+ if (!OVERVIEW_CULLING_ENABLED) return "visible";
179
+ const isWithinViewport =
180
+ alwaysVisible ||
181
+ !viewport ||
182
+ isFrameWithinOverscannedViewport(geometry, viewport);
183
+ if (isWithinViewport) return "visible";
184
+ return hasBeenVisible ? "culled" : "placeholder";
185
+ }
@@ -0,0 +1,120 @@
1
+ import type { GradientLinePoint, Point } from "./types";
2
+
3
+ /**
4
+ * The CSS `linear-gradient()` line-length formula (CSS Images ¤3
5
+ * §linear-gradient-syntax): for a box of `width` x `height` and an angle
6
+ * measured clockwise from "up" (matching both this app's `GradientValue`
7
+ * angle convention and the CSS `<angle>` syntax `linear-gradient(Ndeg, ...)`
8
+ * already produces), the gradient line's half-length from the box center is
9
+ * `|width/2 * sin(angle)| + |height/2 * cos(angle)|` — the projection of the
10
+ * box's half-diagonal onto the gradient axis, so the line exactly spans the
11
+ * box from edge to edge (touching whichever corner is furthest along the
12
+ * axis) the same way a browser renders it.
13
+ */
14
+ export function gradientLineEndpoints(
15
+ angleDeg: number,
16
+ width: number,
17
+ height: number,
18
+ ): { start: Point; end: Point } {
19
+ const rad = (angleDeg * Math.PI) / 180;
20
+ // Unit vector pointing from the gradient's start toward its end (0deg = up
21
+ // = -y, clockwise), matching AngleDial's "0 is north, clockwise" mapping.
22
+ const dx = Math.sin(rad);
23
+ const dy = -Math.cos(rad);
24
+ const halfLength = Math.abs((width / 2) * dx) + Math.abs((height / 2) * dy);
25
+ const center = { x: width / 2, y: height / 2 };
26
+ return {
27
+ start: { x: center.x - dx * halfLength, y: center.y - dy * halfLength },
28
+ end: { x: center.x + dx * halfLength, y: center.y + dy * halfLength },
29
+ };
30
+ }
31
+
32
+ /** Projects each stop's 0–100 `position` onto the gradient line's start/end
33
+ * points (linear interpolation), for rendering a round marker per stop. */
34
+ export function gradientStopPoints(
35
+ angleDeg: number,
36
+ width: number,
37
+ height: number,
38
+ stops: ReadonlyArray<{ position: number }>,
39
+ ): GradientLinePoint[] {
40
+ const { start, end } = gradientLineEndpoints(angleDeg, width, height);
41
+ return stops.map((stop) => {
42
+ const t = clampGradientT(stop.position / 100);
43
+ return {
44
+ x: start.x + (end.x - start.x) * t,
45
+ y: start.y + (end.y - start.y) * t,
46
+ position: stop.position,
47
+ };
48
+ });
49
+ }
50
+
51
+ function clampGradientT(t: number): number {
52
+ if (!Number.isFinite(t)) return 0;
53
+ return Math.max(0, Math.min(1, t));
54
+ }
55
+
56
+ /**
57
+ * Inverts a dragged endpoint handle back into an angle: given the box center
58
+ * and the new local point for whichever endpoint is being dragged (`which`),
59
+ * returns the angle (0-360, "up"-is-0 clockwise) whose gradient line passes
60
+ * through that point. Dragging the "start" handle points the axis the
61
+ * opposite way (the start is the *far* end of the line from that point's
62
+ * direction), so it adds 180° after resolving the raw pointer angle.
63
+ */
64
+ export function angleFromDraggedEndpoint(
65
+ point: Point,
66
+ width: number,
67
+ height: number,
68
+ which: "start" | "end",
69
+ ): number {
70
+ const center = { x: width / 2, y: height / 2 };
71
+ const dx = point.x - center.x;
72
+ const dy = point.y - center.y;
73
+ if (dx === 0 && dy === 0) return 0;
74
+ // atan2 gives angle from east (+x), clockwise-positive in screen space
75
+ // (y-down); convert to this app's "0 is north, clockwise" convention by
76
+ // rotating the reference axis by +90deg, matching AngleDial's own mapping.
77
+ let deg = (Math.atan2(dy, dx) * 180) / Math.PI + 90;
78
+ if (which === "start") deg += 180;
79
+ deg = ((deg % 360) + 360) % 360;
80
+ return deg;
81
+ }
82
+
83
+ /**
84
+ * Inverts a dragged stop marker back into a 0–100 position: projects the
85
+ * dragged local point onto the (unbounded) gradient line through the box
86
+ * center at `angleDeg`, using the *current* line's start/end as the 0/100
87
+ * reference frame, then clamps to [0, 100]. This mirrors
88
+ * `GradientEditor`'s own `positionFromPointer`, which likewise projects the
89
+ * pointer onto the ramp bar's axis and clamps rather than rejecting
90
+ * off-axis drags — on canvas the equivalent "axis" is the gradient line
91
+ * itself, so a drag that strays perpendicular to the line still tracks the
92
+ * nearest point on it instead of doing nothing.
93
+ */
94
+ export function stopPercentFromDraggedPoint(
95
+ point: Point,
96
+ angleDeg: number,
97
+ width: number,
98
+ height: number,
99
+ ): number {
100
+ const { start, end } = gradientLineEndpoints(angleDeg, width, height);
101
+ const lineDx = end.x - start.x;
102
+ const lineDy = end.y - start.y;
103
+ const lengthSquared = lineDx * lineDx + lineDy * lineDy;
104
+ if (lengthSquared === 0) return 0;
105
+ const t =
106
+ ((point.x - start.x) * lineDx + (point.y - start.y) * lineDy) /
107
+ lengthSquared;
108
+ return clampGradientT(t) * 100;
109
+ }
110
+
111
+ /** Converts a constant on-screen pixel radius (e.g. a hit-test tolerance
112
+ * that should feel the same size regardless of zoom) into canvas px, the
113
+ * space hitTestPenAnchor/hitTestPenHandle and PenPath geometry operate in.
114
+ * `zoom` is the same 0-based-at-100 percentage used throughout this file
115
+ * (100 = 1:1). Mirrors the `screenPx / (zoom / 100)` conversion already
116
+ * used by the pen tool's close-hit-target radius. */
117
+ export function screenPxToCanvasPx(screenPx: number, zoom: number): number {
118
+ const scale = zoom / 100;
119
+ return scale > 0 ? screenPx / scale : screenPx;
120
+ }