@1agh/maude 0.28.1 → 0.30.0

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 (372) hide show
  1. package/README.md +3 -3
  2. package/{plugins/design/dev-server → apps/studio}/ai-banner.tsx +1 -2
  3. package/apps/studio/annotations-align.ts +149 -0
  4. package/apps/studio/annotations-bindings.ts +197 -0
  5. package/{plugins/design/dev-server → apps/studio}/annotations-context-toolbar.tsx +681 -151
  6. package/apps/studio/annotations-groups.ts +270 -0
  7. package/apps/studio/annotations-layer.tsx +4537 -0
  8. package/apps/studio/annotations-model.ts +2077 -0
  9. package/apps/studio/annotations-snap.ts +125 -0
  10. package/{plugins/design/dev-server → apps/studio}/api.ts +616 -70
  11. package/{plugins/design/dev-server → apps/studio}/artboard-activity-overlay.tsx +1 -1
  12. package/{plugins/design/dev-server → apps/studio}/artboard-marquee.tsx +1 -1
  13. package/apps/studio/bin/_motion-sample-playwright.mjs +129 -0
  14. package/{plugins/design/dev-server → apps/studio}/bin/_png-playwright.mjs +1 -1
  15. package/apps/studio/bin/annotate.mjs +732 -0
  16. package/apps/studio/bin/annotate.sh +17 -0
  17. package/{plugins/design/dev-server → apps/studio}/bin/canvas-edit.sh +1 -1
  18. package/{plugins/design/dev-server → apps/studio}/bin/draw-proof.sh +37 -2
  19. package/{plugins/design/dev-server → apps/studio}/bin/preflight.sh +1 -1
  20. package/apps/studio/bin/read-annotations.mjs +573 -0
  21. package/apps/studio/bin/read-annotations.sh +11 -0
  22. package/{plugins/design/dev-server → apps/studio}/bin/runtime-health.sh +1 -1
  23. package/{plugins/design/dev-server → apps/studio}/bin/screenshot.sh +62 -2
  24. package/{plugins/design/dev-server → apps/studio}/bin/server-up.sh +9 -3
  25. package/apps/studio/bin/to-lottie-verify.html +68 -0
  26. package/apps/studio/bin/to-lottie.sh +128 -0
  27. package/{plugins/design/dev-server → apps/studio}/build.ts +6 -4
  28. package/{plugins/design/dev-server → apps/studio}/canvas-arrowheads.ts +79 -10
  29. package/{plugins/design/dev-server → apps/studio}/canvas-comment-mount.tsx +1 -1
  30. package/apps/studio/canvas-create.ts +104 -0
  31. package/{plugins/design/dev-server → apps/studio}/canvas-cursors.ts +48 -12
  32. package/{plugins/design/dev-server → apps/studio}/canvas-edit.ts +257 -7
  33. package/{plugins/design/dev-server → apps/studio}/canvas-icons.tsx +165 -1
  34. package/{plugins/design/dev-server → apps/studio}/canvas-lib-inline.ts +1 -1
  35. package/{plugins/design/dev-server → apps/studio}/canvas-lib-resolver.ts +1 -1
  36. package/{plugins/design/dev-server → apps/studio}/canvas-lib.tsx +31 -12
  37. package/{plugins/design/dev-server → apps/studio}/canvas-shell.tsx +360 -48
  38. package/apps/studio/client/app.jsx +6742 -0
  39. package/apps/studio/client/canvas-url.js +81 -0
  40. package/{plugins/design/dev-server → apps/studio}/client/comments-overlay.css +41 -27
  41. package/apps/studio/client/index.html +16 -0
  42. package/apps/studio/client/styles/1-tokens-maude.css +296 -0
  43. package/apps/studio/client/styles/3-shell-maude.css +1161 -0
  44. package/{plugins/design/dev-server → apps/studio}/client/styles/3-shell.css +129 -0
  45. package/apps/studio/client/styles/4-components-maude.css +74 -0
  46. package/{plugins/design/dev-server → apps/studio}/client/styles/4-components.css +396 -34
  47. package/apps/studio/client/styles/5-maude-overrides.css +137 -0
  48. package/{plugins/design/dev-server → apps/studio}/client/styles/_index.css +9 -0
  49. package/{plugins/design/dev-server → apps/studio}/client/styles.css +1 -1
  50. package/apps/studio/client/tour/overlay.jsx +279 -0
  51. package/apps/studio/client/tour/usage-tour.js +48 -0
  52. package/apps/studio/client/whats-new-seen.js +52 -0
  53. package/apps/studio/client/whats-new.jsx +250 -0
  54. package/{plugins/design/dev-server → apps/studio}/commands/annotation-strokes-command.ts +2 -2
  55. package/{plugins/design/dev-server → apps/studio}/commands/equal-spacing-command.ts +1 -1
  56. package/{plugins/design/dev-server → apps/studio}/commands/move-artboards-command.ts +2 -2
  57. package/{plugins/design/dev-server → apps/studio}/comments-overlay.tsx +32 -11
  58. package/{plugins/design/dev-server → apps/studio}/context-menu.tsx +5 -4
  59. package/{plugins/design/dev-server → apps/studio}/contextual-toolbar.tsx +18 -7
  60. package/{plugins/design/dev-server → apps/studio}/cursors-overlay.tsx +21 -21
  61. package/apps/studio/dist/client.bundle.js +25545 -0
  62. package/apps/studio/dist/comment-mount.js +2046 -0
  63. package/apps/studio/dist/styles.css +8018 -0
  64. package/apps/studio/dom-selection.ts +342 -0
  65. package/apps/studio/draw/animate.ts +333 -0
  66. package/{plugins/design/dev-server → apps/studio}/draw/brush.ts +1 -1
  67. package/{plugins/design/dev-server → apps/studio}/draw/composition.ts +1 -1
  68. package/{plugins/design/dev-server → apps/studio}/draw/geometry.ts +1 -1
  69. package/{plugins/design/dev-server → apps/studio}/draw/index.ts +4 -1
  70. package/{plugins/design/dev-server → apps/studio}/draw/layout.ts +1 -1
  71. package/apps/studio/draw/morph.ts +204 -0
  72. package/{plugins/design/dev-server → apps/studio}/draw/optimize.ts +1 -1
  73. package/{plugins/design/dev-server → apps/studio}/draw/palette.ts +1 -1
  74. package/{plugins/design/dev-server → apps/studio}/draw/primitives.ts +1 -1
  75. package/apps/studio/draw/serialize-animate.ts +324 -0
  76. package/{plugins/design/dev-server → apps/studio}/draw/serialize.ts +9 -3
  77. package/apps/studio/draw/test/animate.test.ts +172 -0
  78. package/apps/studio/draw/test/morph.test.ts +107 -0
  79. package/apps/studio/draw/test/serialize-animate.test.ts +142 -0
  80. package/{plugins/design/dev-server → apps/studio}/equal-spacing-detector.ts +1 -1
  81. package/{plugins/design/dev-server → apps/studio}/equal-spacing-handles.tsx +7 -29
  82. package/{plugins/design/dev-server → apps/studio}/export-dialog.tsx +16 -4
  83. package/{plugins/design/dev-server → apps/studio}/history.ts +20 -3
  84. package/{plugins/design/dev-server → apps/studio}/http.ts +226 -6
  85. package/{plugins/design/dev-server → apps/studio}/input-router.tsx +15 -1
  86. package/{plugins/design/dev-server → apps/studio}/inspect.ts +50 -4
  87. package/{plugins/design/dev-server → apps/studio}/marquee-overlay.tsx +5 -2
  88. package/{plugins/design/dev-server → apps/studio}/participants-chrome.tsx +2 -8
  89. package/{plugins/design/dev-server → apps/studio}/paths.ts +15 -11
  90. package/{plugins/design/dev-server → apps/studio}/server.ts +23 -2
  91. package/{plugins/design/dev-server → apps/studio}/sync/agent.ts +231 -57
  92. package/{plugins/design/dev-server → apps/studio}/sync/codec.ts +45 -0
  93. package/apps/studio/sync/cold-start.ts +158 -0
  94. package/{plugins/design/dev-server → apps/studio}/sync/connection-state.ts +58 -2
  95. package/{plugins/design/dev-server → apps/studio}/sync/index.ts +563 -221
  96. package/apps/studio/sync/journal.ts +190 -0
  97. package/apps/studio/sync/migrate-seed.ts +321 -0
  98. package/{plugins/design/dev-server → apps/studio}/sync/projection.ts +22 -3
  99. package/{plugins/design/dev-server → apps/studio}/sync/status.ts +15 -4
  100. package/{plugins/design/dev-server → apps/studio}/test/activity.test.ts +1 -7
  101. package/apps/studio/test/annotate-write.test.ts +184 -0
  102. package/apps/studio/test/annotations-align.test.ts +88 -0
  103. package/apps/studio/test/annotations-bindings.test.ts +124 -0
  104. package/apps/studio/test/annotations-groups.test.ts +231 -0
  105. package/{plugins/design/dev-server → apps/studio}/test/annotations-layer.test.ts +63 -0
  106. package/{plugins/design/dev-server → apps/studio}/test/annotations-roundtrip.test.ts +97 -0
  107. package/apps/studio/test/annotations-snap.test.ts +79 -0
  108. package/apps/studio/test/annotations-text-format.test.ts +396 -0
  109. package/apps/studio/test/asset-api.test.ts +149 -0
  110. package/apps/studio/test/canvas-create-api.test.ts +361 -0
  111. package/{plugins/design/dev-server → apps/studio}/test/canvas-cursors.test.ts +2 -0
  112. package/apps/studio/test/canvas-edit.test.ts +319 -0
  113. package/{plugins/design/dev-server → apps/studio}/test/canvas-lib-resolver.test.ts +1 -1
  114. package/apps/studio/test/canvas-media-drop.test.ts +158 -0
  115. package/{plugins/design/dev-server → apps/studio}/test/canvas-origin-gate.test.ts +25 -1
  116. package/apps/studio/test/canvas-url.test.ts +99 -0
  117. package/{plugins/design/dev-server → apps/studio}/test/collab-bridge.test.ts +0 -2
  118. package/{plugins/design/dev-server → apps/studio}/test/collab-room.test.ts +2 -7
  119. package/{plugins/design/dev-server → apps/studio}/test/csp-canvas-shell.test.ts +5 -0
  120. package/apps/studio/test/csrf-write-guard.test.ts +52 -0
  121. package/apps/studio/test/figjam-v3-model.test.ts +342 -0
  122. package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +1 -0
  123. package/apps/studio/test/index-data-canvas-ds.test.ts +97 -0
  124. package/apps/studio/test/motion-sample.test.ts +35 -0
  125. package/{plugins/design/dev-server → apps/studio}/test/perf-harness.ts +1 -1
  126. package/{plugins/design/dev-server → apps/studio}/test/phase-3.6-smoke.test.ts +3 -1
  127. package/apps/studio/test/read-annotations.test.ts +409 -0
  128. package/{plugins/design/dev-server → apps/studio}/test/sanitize-annotation-svg.test.ts +108 -2
  129. package/{plugins/design/dev-server → apps/studio}/test/shared-doc-convergence.test.ts +1 -1
  130. package/apps/studio/test/shared-doc-migrate.test.ts +369 -0
  131. package/apps/studio/test/sync-agent.test.ts +575 -0
  132. package/{plugins/design/dev-server → apps/studio}/test/sync-codec.test.ts +65 -0
  133. package/apps/studio/test/sync-cold-start.test.ts +244 -0
  134. package/{plugins/design/dev-server → apps/studio}/test/sync-connection-state.test.ts +68 -0
  135. package/{plugins/design/dev-server → apps/studio}/test/sync-hardening.test.ts +0 -8
  136. package/{plugins/design/dev-server → apps/studio}/test/sync-hubs-config.test.ts +0 -1
  137. package/apps/studio/test/sync-incident-replay.test.ts +211 -0
  138. package/apps/studio/test/sync-journal.test.ts +176 -0
  139. package/{plugins/design/dev-server → apps/studio}/test/sync-runtime.test.ts +360 -5
  140. package/{plugins/design/dev-server → apps/studio}/test/sync-status.test.ts +66 -0
  141. package/apps/studio/test/tour-overlay.test.tsx +56 -0
  142. package/apps/studio/test/use-annotation-resize.test.ts +402 -0
  143. package/{plugins/design/dev-server → apps/studio}/test/use-artboard-drag.test.ts +0 -1
  144. package/{plugins/design/dev-server → apps/studio}/test/use-collab.test.ts +0 -0
  145. package/{plugins/design/dev-server → apps/studio}/test/use-tool-mode.test.tsx +8 -2
  146. package/apps/studio/test/whats-new.test.ts +101 -0
  147. package/apps/studio/text-imports.d.ts +9 -0
  148. package/{plugins/design/dev-server → apps/studio}/tool-palette.tsx +15 -70
  149. package/{plugins/design/dev-server → apps/studio}/undo-hud.tsx +1 -2
  150. package/{plugins/design/dev-server → apps/studio}/undo-stack.ts +1 -1
  151. package/{plugins/design/dev-server → apps/studio}/use-agent-presence.tsx +5 -3
  152. package/apps/studio/use-annotation-resize.tsx +912 -0
  153. package/{plugins/design/dev-server → apps/studio}/use-annotation-selection.tsx +17 -5
  154. package/{plugins/design/dev-server → apps/studio}/use-annotations-visibility.tsx +1 -1
  155. package/{plugins/design/dev-server → apps/studio}/use-artboard-drag.tsx +1 -1
  156. package/{plugins/design/dev-server → apps/studio}/use-canvas-activity.tsx +3 -3
  157. package/apps/studio/use-canvas-media-drop.tsx +343 -0
  158. package/{plugins/design/dev-server → apps/studio}/use-collab.tsx +21 -16
  159. package/{plugins/design/dev-server → apps/studio}/use-cursor-modifiers.tsx +1 -1
  160. package/{plugins/design/dev-server → apps/studio}/use-keyboard-discipline.tsx +5 -2
  161. package/{plugins/design/dev-server → apps/studio}/use-selection-set.tsx +18 -1
  162. package/{plugins/design/dev-server → apps/studio}/use-snap-guides.tsx +1 -1
  163. package/{plugins/design/dev-server → apps/studio}/use-tool-mode.tsx +8 -2
  164. package/{plugins/design/dev-server → apps/studio}/use-undo-stack.tsx +1 -1
  165. package/apps/studio/whats-new.json +264 -0
  166. package/apps/studio/whats-new.schema.json +86 -0
  167. package/apps/studio/whats-new.ts +87 -0
  168. package/cli/bin/maude.mjs +10 -0
  169. package/cli/commands/design-link.test.mjs +84 -0
  170. package/cli/commands/design.mjs +16 -7
  171. package/cli/commands/design.test.mjs +40 -0
  172. package/cli/commands/help.mjs +8 -2
  173. package/cli/commands/hub.mjs +3 -3
  174. package/cli/commands/hub.test.mjs +1 -1
  175. package/cli/commands/scenario-report.mjs +2 -9
  176. package/cli/lib/design-link.mjs +51 -1
  177. package/cli/lib/flow-design-integration.test.mjs +1 -1
  178. package/cli/lib/gitignore-block.mjs +1 -0
  179. package/cli/lib/plugin-cli-reachability.test.mjs +8 -7
  180. package/cli/lib/preflight.mjs +1 -1
  181. package/package.json +15 -15
  182. package/plugins/design/templates/_shell.html +28 -4
  183. package/plugins/design/templates/brief-board.tsx.template +61 -0
  184. package/plugins/design/templates/canvas.tsx.template +1 -1
  185. package/plugins/design/templates/design-system-inspiration/core/config.json.tpl +1 -1
  186. package/plugins/design/templates/design-system-inspiration/core/preview/_motion-readme.md.tpl +1 -1
  187. package/plugins/flow/.claude-plugin/config.schema.json +16 -0
  188. package/plugins/design/dev-server/annotations-layer.tsx +0 -3402
  189. package/plugins/design/dev-server/client/app.jsx +0 -2985
  190. package/plugins/design/dev-server/client/index.html +0 -15
  191. package/plugins/design/dev-server/dist/client.bundle.js +0 -20692
  192. package/plugins/design/dev-server/dist/comment-mount.js +0 -1924
  193. package/plugins/design/dev-server/dist/styles.css +0 -2710
  194. package/plugins/design/dev-server/dom-selection.ts +0 -156
  195. package/plugins/design/dev-server/sync/migrate-seed.ts +0 -163
  196. package/plugins/design/dev-server/test/canvas-edit.test.ts +0 -139
  197. package/plugins/design/dev-server/test/shared-doc-migrate.test.ts +0 -159
  198. package/plugins/design/dev-server/test/sync-agent.test.ts +0 -305
  199. package/plugins/design/dev-server/test/use-annotation-resize.test.ts +0 -200
  200. package/plugins/design/dev-server/use-annotation-resize.tsx +0 -449
  201. /package/{plugins/design/dev-server → apps/studio}/activity.ts +0 -0
  202. /package/{plugins/design/dev-server → apps/studio}/bin/_enumerate-artboards-playwright.mjs +0 -0
  203. /package/{plugins/design/dev-server → apps/studio}/bin/_html-playwright.mjs +0 -0
  204. /package/{plugins/design/dev-server → apps/studio}/bin/_pdf-playwright.mjs +0 -0
  205. /package/{plugins/design/dev-server → apps/studio}/bin/_pptx-playwright.mjs +0 -0
  206. /package/{plugins/design/dev-server → apps/studio}/bin/_pw-launch.mjs +0 -0
  207. /package/{plugins/design/dev-server → apps/studio}/bin/_screenshot-playwright.mjs +0 -0
  208. /package/{plugins/design/dev-server → apps/studio}/bin/_svg-optimize.mjs +0 -0
  209. /package/{plugins/design/dev-server → apps/studio}/bin/_svg-playwright.mjs +0 -0
  210. /package/{plugins/design/dev-server → apps/studio}/bin/asset-sweep.sh +0 -0
  211. /package/{plugins/design/dev-server → apps/studio}/bin/bootstrap-check.sh +0 -0
  212. /package/{plugins/design/dev-server → apps/studio}/bin/check-runtime-bundles.sh +0 -0
  213. /package/{plugins/design/dev-server → apps/studio}/bin/draw-build.sh +0 -0
  214. /package/{plugins/design/dev-server → apps/studio}/bin/handoff.sh +0 -0
  215. /package/{plugins/design/dev-server → apps/studio}/bin/prep.sh +0 -0
  216. /package/{plugins/design/dev-server → apps/studio}/bin/scenario-report.mjs +0 -0
  217. /package/{plugins/design/dev-server → apps/studio}/bin/slug.sh +0 -0
  218. /package/{plugins/design/dev-server → apps/studio}/bin/smoke.sh +0 -0
  219. /package/{plugins/design/dev-server → apps/studio}/bin/svg-optimize.sh +0 -0
  220. /package/{plugins/design/dev-server → apps/studio}/bin/visual-sanity.sh +0 -0
  221. /package/{plugins/design/dev-server → apps/studio}/boot-self-heal.ts +0 -0
  222. /package/{plugins/design/dev-server → apps/studio}/canvas-build.ts +0 -0
  223. /package/{plugins/design/dev-server → apps/studio}/canvas-header.ts +0 -0
  224. /package/{plugins/design/dev-server → apps/studio}/canvas-meta.schema.json +0 -0
  225. /package/{plugins/design/dev-server → apps/studio}/canvas-pipeline.ts +0 -0
  226. /package/{plugins/design/dev-server → apps/studio}/client/hmr.mjs +0 -0
  227. /package/{plugins/design/dev-server → apps/studio}/client/iframe-lazy.mjs +0 -0
  228. /package/{plugins/design/dev-server → apps/studio}/client/styles/0-reset.css +0 -0
  229. /package/{plugins/design/dev-server → apps/studio}/client/styles/1-tokens.css +0 -0
  230. /package/{plugins/design/dev-server → apps/studio}/client/styles/2-layout.css +0 -0
  231. /package/{plugins/design/dev-server → apps/studio}/client/styles/5-utilities.css +0 -0
  232. /package/{plugins/design/dev-server → apps/studio}/collab/ai-activity.ts +0 -0
  233. /package/{plugins/design/dev-server → apps/studio}/collab/awareness-bridge.ts +0 -0
  234. /package/{plugins/design/dev-server → apps/studio}/collab/git-lifecycle.ts +0 -0
  235. /package/{plugins/design/dev-server → apps/studio}/collab/index.ts +0 -0
  236. /package/{plugins/design/dev-server → apps/studio}/collab/persistence.ts +0 -0
  237. /package/{plugins/design/dev-server → apps/studio}/collab/protocol.ts +0 -0
  238. /package/{plugins/design/dev-server → apps/studio}/collab/registry.ts +0 -0
  239. /package/{plugins/design/dev-server → apps/studio}/collab/room.ts +0 -0
  240. /package/{plugins/design/dev-server → apps/studio}/config.schema.json +0 -0
  241. /package/{plugins/design/dev-server → apps/studio}/context.ts +0 -0
  242. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/.min-sizes.json +0 -0
  243. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/lib0_decoding.js +0 -0
  244. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/lib0_encoding.js +0 -0
  245. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/motion.js +0 -0
  246. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/motion_react.js +0 -0
  247. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/pixi-js.js +0 -0
  248. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/react-dom.js +0 -0
  249. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/react-dom_client.js +0 -0
  250. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/react.js +0 -0
  251. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/react_jsx-dev-runtime.js +0 -0
  252. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/react_jsx-runtime.js +0 -0
  253. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/y-protocols_awareness.js +0 -0
  254. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/y-protocols_sync.js +0 -0
  255. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/yjs.js +0 -0
  256. /package/{plugins/design/dev-server → apps/studio}/draw/test/brush.test.ts +0 -0
  257. /package/{plugins/design/dev-server → apps/studio}/draw/test/composition.test.ts +0 -0
  258. /package/{plugins/design/dev-server → apps/studio}/draw/test/geometry.test.ts +0 -0
  259. /package/{plugins/design/dev-server → apps/studio}/draw/test/gradient.test.ts +0 -0
  260. /package/{plugins/design/dev-server → apps/studio}/draw/test/layout.test.ts +0 -0
  261. /package/{plugins/design/dev-server → apps/studio}/draw/test/optimize.test.ts +0 -0
  262. /package/{plugins/design/dev-server → apps/studio}/draw/test/palette.test.ts +0 -0
  263. /package/{plugins/design/dev-server → apps/studio}/draw/test/primitives.test.ts +0 -0
  264. /package/{plugins/design/dev-server → apps/studio}/draw/test/serialize.test.ts +0 -0
  265. /package/{plugins/design/dev-server → apps/studio}/examples/README.md +0 -0
  266. /package/{plugins/design/dev-server → apps/studio}/examples/perf-100-artboards.tsx +0 -0
  267. /package/{plugins/design/dev-server → apps/studio}/exporters/_browser-bundles.ts +0 -0
  268. /package/{plugins/design/dev-server → apps/studio}/exporters/canva-handoff-prompt.ts +0 -0
  269. /package/{plugins/design/dev-server → apps/studio}/exporters/canva.ts +0 -0
  270. /package/{plugins/design/dev-server → apps/studio}/exporters/html.ts +0 -0
  271. /package/{plugins/design/dev-server → apps/studio}/exporters/index.ts +0 -0
  272. /package/{plugins/design/dev-server → apps/studio}/exporters/pdf.ts +0 -0
  273. /package/{plugins/design/dev-server → apps/studio}/exporters/png.ts +0 -0
  274. /package/{plugins/design/dev-server → apps/studio}/exporters/pptx.ts +0 -0
  275. /package/{plugins/design/dev-server → apps/studio}/exporters/scope.ts +0 -0
  276. /package/{plugins/design/dev-server → apps/studio}/exporters/svg.ts +0 -0
  277. /package/{plugins/design/dev-server → apps/studio}/exporters/zip.ts +0 -0
  278. /package/{plugins/design/dev-server → apps/studio}/fs-watch.ts +0 -0
  279. /package/{plugins/design/dev-server → apps/studio}/handoff.ts +0 -0
  280. /package/{plugins/design/dev-server → apps/studio}/hmr-broadcast.ts +0 -0
  281. /package/{plugins/design/dev-server → apps/studio}/locator.ts +0 -0
  282. /package/{plugins/design/dev-server → apps/studio}/mem.ts +0 -0
  283. /package/{plugins/design/dev-server → apps/studio}/runtime-bundle.ts +0 -0
  284. /package/{plugins/design/dev-server → apps/studio}/server.mjs +0 -0
  285. /package/{plugins/design/dev-server → apps/studio}/sync/atomic-write.ts +0 -0
  286. /package/{plugins/design/dev-server → apps/studio}/sync/echo-guard.ts +0 -0
  287. /package/{plugins/design/dev-server → apps/studio}/sync/fs-mirror.ts +0 -0
  288. /package/{plugins/design/dev-server → apps/studio}/sync/hubs-config.ts +0 -0
  289. /package/{plugins/design/dev-server → apps/studio}/sync/materialize.ts +0 -0
  290. /package/{plugins/design/dev-server → apps/studio}/sync/origins.ts +0 -0
  291. /package/{plugins/design/dev-server → apps/studio}/sync/untrusted.ts +0 -0
  292. /package/{plugins/design/dev-server → apps/studio}/test/_helpers.ts +0 -0
  293. /package/{plugins/design/dev-server → apps/studio}/test/active-state.test.ts +0 -0
  294. /package/{plugins/design/dev-server → apps/studio}/test/ai-activity.test.ts +0 -0
  295. /package/{plugins/design/dev-server → apps/studio}/test/annotation-strokes-command.test.ts +0 -0
  296. /package/{plugins/design/dev-server → apps/studio}/test/annotations-api.test.ts +0 -0
  297. /package/{plugins/design/dev-server → apps/studio}/test/annotations-draw-modifiers.test.ts +0 -0
  298. /package/{plugins/design/dev-server → apps/studio}/test/artboard-activity-overlay.test.tsx +0 -0
  299. /package/{plugins/design/dev-server → apps/studio}/test/binary-smoke.test.ts +0 -0
  300. /package/{plugins/design/dev-server → apps/studio}/test/boot-self-heal.test.ts +0 -0
  301. /package/{plugins/design/dev-server → apps/studio}/test/bundle-smoke.test.ts +0 -0
  302. /package/{plugins/design/dev-server → apps/studio}/test/canvas-build.test.ts +0 -0
  303. /package/{plugins/design/dev-server → apps/studio}/test/canvas-header.test.ts +0 -0
  304. /package/{plugins/design/dev-server → apps/studio}/test/canvas-hmr-runtime.test.tsx +0 -0
  305. /package/{plugins/design/dev-server → apps/studio}/test/canvas-lib-inline.test.ts +0 -0
  306. /package/{plugins/design/dev-server → apps/studio}/test/canvas-lib-motion.test.ts +0 -0
  307. /package/{plugins/design/dev-server → apps/studio}/test/canvas-meta-api.test.ts +0 -0
  308. /package/{plugins/design/dev-server → apps/studio}/test/canvas-pipeline.test.ts +0 -0
  309. /package/{plugins/design/dev-server → apps/studio}/test/canvas-route.test.ts +0 -0
  310. /package/{plugins/design/dev-server → apps/studio}/test/collab-annotations-bridge.test.ts +0 -0
  311. /package/{plugins/design/dev-server → apps/studio}/test/collab-awareness-bridge.test.ts +0 -0
  312. /package/{plugins/design/dev-server → apps/studio}/test/collab-loopback.test.ts +0 -0
  313. /package/{plugins/design/dev-server → apps/studio}/test/collab-protocol.test.ts +0 -0
  314. /package/{plugins/design/dev-server → apps/studio}/test/collab-reseed-guard.test.ts +0 -0
  315. /package/{plugins/design/dev-server → apps/studio}/test/collab-stress.test.ts +0 -0
  316. /package/{plugins/design/dev-server → apps/studio}/test/comment-mount.test.ts +0 -0
  317. /package/{plugins/design/dev-server → apps/studio}/test/comments-api.test.ts +0 -0
  318. /package/{plugins/design/dev-server → apps/studio}/test/compile-entry.test.ts +0 -0
  319. /package/{plugins/design/dev-server → apps/studio}/test/context-resolve-tokens.test.ts +0 -0
  320. /package/{plugins/design/dev-server → apps/studio}/test/equal-spacing-detector.test.ts +0 -0
  321. /package/{plugins/design/dev-server → apps/studio}/test/exporters/canva.test.ts +0 -0
  322. /package/{plugins/design/dev-server → apps/studio}/test/exporters/endpoint.test.ts +0 -0
  323. /package/{plugins/design/dev-server → apps/studio}/test/exporters/history.test.ts +0 -0
  324. /package/{plugins/design/dev-server → apps/studio}/test/exporters/html.test.ts +0 -0
  325. /package/{plugins/design/dev-server → apps/studio}/test/exporters/pdf.test.ts +0 -0
  326. /package/{plugins/design/dev-server → apps/studio}/test/exporters/png.test.ts +0 -0
  327. /package/{plugins/design/dev-server → apps/studio}/test/exporters/pptx-deck.test.ts +0 -0
  328. /package/{plugins/design/dev-server → apps/studio}/test/exporters/pptx.test.ts +0 -0
  329. /package/{plugins/design/dev-server → apps/studio}/test/exporters/pw-launch.test.ts +0 -0
  330. /package/{plugins/design/dev-server → apps/studio}/test/exporters/scope.test.ts +0 -0
  331. /package/{plugins/design/dev-server → apps/studio}/test/exporters/svg.test.ts +0 -0
  332. /package/{plugins/design/dev-server → apps/studio}/test/exporters/zip.test.ts +0 -0
  333. /package/{plugins/design/dev-server → apps/studio}/test/fixtures/phase-20-annotations.svg +0 -0
  334. /package/{plugins/design/dev-server → apps/studio}/test/fixtures/phase-21-annotations.svg +0 -0
  335. /package/{plugins/design/dev-server → apps/studio}/test/fs-watch.test.ts +0 -0
  336. /package/{plugins/design/dev-server → apps/studio}/test/git-lifecycle.test.ts +0 -0
  337. /package/{plugins/design/dev-server → apps/studio}/test/handoff-static-frames.test.ts +0 -0
  338. /package/{plugins/design/dev-server → apps/studio}/test/handoff.test.ts +0 -0
  339. /package/{plugins/design/dev-server → apps/studio}/test/history-rollback.test.ts +0 -0
  340. /package/{plugins/design/dev-server → apps/studio}/test/hmr-broadcast.test.ts +0 -0
  341. /package/{plugins/design/dev-server → apps/studio}/test/hmr-classify.test.ts +0 -0
  342. /package/{plugins/design/dev-server → apps/studio}/test/input-router.test.ts +0 -0
  343. /package/{plugins/design/dev-server → apps/studio}/test/locator.test.ts +0 -0
  344. /package/{plugins/design/dev-server → apps/studio}/test/marquee-overlay.test.ts +0 -0
  345. /package/{plugins/design/dev-server → apps/studio}/test/move-artboards-command.test.ts +0 -0
  346. /package/{plugins/design/dev-server → apps/studio}/test/participants-chrome.test.ts +0 -0
  347. /package/{plugins/design/dev-server → apps/studio}/test/paths.test.ts +0 -0
  348. /package/{plugins/design/dev-server → apps/studio}/test/runtime-bundle-error-mapping.test.ts +0 -0
  349. /package/{plugins/design/dev-server → apps/studio}/test/runtime-bundle.test.ts +0 -0
  350. /package/{plugins/design/dev-server → apps/studio}/test/server-lifecycle.test.ts +0 -0
  351. /package/{plugins/design/dev-server → apps/studio}/test/shared-doc-foundation.test.ts +0 -0
  352. /package/{plugins/design/dev-server → apps/studio}/test/shared-doc-projection.test.ts +0 -0
  353. /package/{plugins/design/dev-server → apps/studio}/test/snap-distance-pill.test.ts +0 -0
  354. /package/{plugins/design/dev-server → apps/studio}/test/sync-atomic-write.test.ts +0 -0
  355. /package/{plugins/design/dev-server → apps/studio}/test/sync-echo-guard.test.ts +0 -0
  356. /package/{plugins/design/dev-server → apps/studio}/test/sync-fs-mirror.test.ts +0 -0
  357. /package/{plugins/design/dev-server → apps/studio}/test/sync-meta-codec.test.ts +0 -0
  358. /package/{plugins/design/dev-server → apps/studio}/test/sync-untrusted.test.ts +0 -0
  359. /package/{plugins/design/dev-server → apps/studio}/test/system-endpoint.test.ts +0 -0
  360. /package/{plugins/design/dev-server → apps/studio}/test/tool-palette.test.tsx +0 -0
  361. /package/{plugins/design/dev-server → apps/studio}/test/undo-stack.test.ts +0 -0
  362. /package/{plugins/design/dev-server → apps/studio}/test/use-agent-presence.test.tsx +0 -0
  363. /package/{plugins/design/dev-server → apps/studio}/test/use-annotation-selection.test.tsx +0 -0
  364. /package/{plugins/design/dev-server → apps/studio}/test/use-canvas-activity.test.tsx +0 -0
  365. /package/{plugins/design/dev-server → apps/studio}/test/use-cursor-modifiers.test.ts +0 -0
  366. /package/{plugins/design/dev-server → apps/studio}/test/use-keyboard-discipline.test.ts +0 -0
  367. /package/{plugins/design/dev-server → apps/studio}/test/use-selection-set.test.tsx +0 -0
  368. /package/{plugins/design/dev-server → apps/studio}/test/use-snap-guides.test.ts +0 -0
  369. /package/{plugins/design/dev-server → apps/studio}/test/use-undo-stack.test.tsx +0 -0
  370. /package/{plugins/design/dev-server → apps/studio}/test/ws-handshake.test.ts +0 -0
  371. /package/{plugins/design/dev-server → apps/studio}/tsconfig.json +0 -0
  372. /package/{plugins/design/dev-server → apps/studio}/ws.ts +0 -0
@@ -0,0 +1,172 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import {
3
+ type CubicBezierHandles,
4
+ easeBezier,
5
+ LINEAR,
6
+ lerpValue,
7
+ lerpVertices,
8
+ parallel,
9
+ resolveStagger,
10
+ sampleTrack,
11
+ sequence,
12
+ shiftTrack,
13
+ stagger,
14
+ type Track,
15
+ timeline,
16
+ track,
17
+ trackSpan,
18
+ } from '../animate.ts';
19
+
20
+ describe('easeBezier', () => {
21
+ test('linear is the identity', () => {
22
+ for (const x of [0, 0.25, 0.5, 0.75, 1]) {
23
+ expect(easeBezier(LINEAR, x)).toBeCloseTo(x, 6);
24
+ }
25
+ });
26
+ test('clamps the endpoints exactly', () => {
27
+ const e: CubicBezierHandles = [0.42, 0, 0.58, 1];
28
+ expect(easeBezier(e, 0)).toBe(0);
29
+ expect(easeBezier(e, 1)).toBe(1);
30
+ expect(easeBezier(e, -0.5)).toBe(0);
31
+ expect(easeBezier(e, 1.5)).toBe(1);
32
+ });
33
+ test('ease-in-out is symmetric about the midpoint and monotone', () => {
34
+ const e: CubicBezierHandles = [0.42, 0, 0.58, 1];
35
+ expect(easeBezier(e, 0.5)).toBeCloseTo(0.5, 4);
36
+ let prev = -1;
37
+ for (let i = 0; i <= 20; i++) {
38
+ const y = easeBezier(e, i / 20);
39
+ expect(y).toBeGreaterThanOrEqual(prev - 1e-9);
40
+ prev = y;
41
+ }
42
+ });
43
+ test('preserves overshoot (y > 1) for a snap easing', () => {
44
+ const snap: CubicBezierHandles = [0.34, 1.42, 0.64, 1];
45
+ let maxY = 0;
46
+ for (let i = 0; i <= 100; i++) maxY = Math.max(maxY, easeBezier(snap, i / 100));
47
+ expect(maxY).toBeGreaterThan(1); // the overshoot the snap depends on
48
+ });
49
+ test('round-trips a known x→t→y solve (ease-out)', () => {
50
+ // cubic-bezier(0,0,0.58,1): solving x then evaluating y must land in (x,1).
51
+ const e: CubicBezierHandles = [0, 0, 0.58, 1];
52
+ const y = easeBezier(e, 0.5);
53
+ expect(y).toBeGreaterThan(0.5); // ease-out is ahead of linear at the midpoint
54
+ expect(y).toBeLessThan(1);
55
+ });
56
+ });
57
+
58
+ describe('lerpValue / lerpVertices', () => {
59
+ test('numbers lerp', () => {
60
+ expect(lerpValue(0, 10, 0.3)).toBeCloseTo(3, 6);
61
+ });
62
+ test('number arrays lerp element-wise', () => {
63
+ expect(lerpValue([0, 10], [10, 0], 0.5)).toEqual([5, 5]);
64
+ });
65
+ test('vertex arrays lerp by component', () => {
66
+ const out = lerpVertices([{ x: 0, y: 0 }], [{ x: 4, y: 8 }], 0.25);
67
+ expect(out).toEqual([{ x: 1, y: 2 }]);
68
+ });
69
+ test('strings are a discrete hold (no interpolation)', () => {
70
+ expect(lerpValue('rotate(0)', 'rotate(90)', 0.5)).toBe('rotate(0)');
71
+ expect(lerpValue('rotate(0)', 'rotate(90)', 1)).toBe('rotate(90)');
72
+ });
73
+ test('vertex count mismatch throws (interpolability constraint)', () => {
74
+ expect(() => lerpVertices([{ x: 0, y: 0 }], [], 0.5)).toThrow(/vertex count mismatch/);
75
+ });
76
+ });
77
+
78
+ describe('sampleTrack', () => {
79
+ const tr = track('opacity', [
80
+ { t: 0, value: 0, ease: LINEAR },
81
+ { t: 1, value: 1 },
82
+ ]);
83
+ test('holds before the first and after the last keyframe', () => {
84
+ expect(sampleTrack(tr, -1)).toBe(0);
85
+ expect(sampleTrack(tr, 5)).toBe(1);
86
+ });
87
+ test('linear interpolation mid-segment', () => {
88
+ expect(sampleTrack(tr, 0.5)).toBeCloseTo(0.5, 6);
89
+ });
90
+ test('applies the leading keyframe easing', () => {
91
+ const eased = track('opacity', [
92
+ { t: 0, value: 0, ease: [0, 0, 0.58, 1] }, // ease-out
93
+ { t: 1, value: 1 },
94
+ ]);
95
+ expect(sampleTrack(eased, 0.5) as number).toBeGreaterThan(0.5);
96
+ });
97
+ test('empty track samples undefined', () => {
98
+ expect(sampleTrack(track('opacity', []), 0)).toBeUndefined();
99
+ });
100
+ });
101
+
102
+ describe('combinators', () => {
103
+ const a = (): Track =>
104
+ track(
105
+ 'opacity',
106
+ [
107
+ { t: 0, value: 0 },
108
+ { t: 1, value: 1 },
109
+ ],
110
+ 'a'
111
+ );
112
+ const b = (): Track =>
113
+ track(
114
+ 'opacity',
115
+ [
116
+ { t: 0, value: 1 },
117
+ { t: 2, value: 0 },
118
+ ],
119
+ 'b'
120
+ );
121
+
122
+ test('trackSpan is the latest keyframe time', () => {
123
+ expect(trackSpan([a(), b()])).toBe(2);
124
+ });
125
+
126
+ test('shiftTrack offsets every keyframe', () => {
127
+ const s = shiftTrack(a(), 3);
128
+ expect(s.keyframes.map((k) => k.t)).toEqual([3, 4]);
129
+ expect(a().keyframes[0].t).toBe(0); // immutable
130
+ });
131
+
132
+ test('stagger offsets track i by i·delay', () => {
133
+ const out = stagger([a(), b(), a()], 0.5);
134
+ expect(out[0].keyframes[0].t).toBe(0);
135
+ expect(out[1].keyframes[0].t).toBe(0.5);
136
+ expect(out[2].keyframes[0].t).toBe(1);
137
+ });
138
+
139
+ test('resolveStagger bakes the declarative field and grows dur', () => {
140
+ const tl = timeline({ tracks: [a(), b()], dur: 2, stagger: 0.5 });
141
+ const r = resolveStagger(tl);
142
+ expect(r.stagger).toBeUndefined();
143
+ expect(r.tracks[1].keyframes[0].t).toBe(0.5);
144
+ expect(r.dur).toBe(2.5); // b ended at 2 → shifted to 2.5
145
+ });
146
+
147
+ test('parallel unions tracks and takes the max duration', () => {
148
+ const tl = parallel(
149
+ timeline({ tracks: [a()], dur: 1 }),
150
+ timeline({ tracks: [b()], dur: 2, begin: 0.5 })
151
+ );
152
+ expect(tl.tracks.length).toBe(2);
153
+ expect(tl.dur).toBe(2.5); // 0.5 begin + 2 dur
154
+ expect(tl.tracks[1].keyframes[0].t).toBe(0.5); // b shifted by its begin
155
+ });
156
+
157
+ test('sequence chains timelines end-to-start and sums durations', () => {
158
+ const tl = sequence(timeline({ tracks: [a()], dur: 1 }), timeline({ tracks: [b()], dur: 2 }));
159
+ expect(tl.dur).toBe(3);
160
+ expect(tl.tracks[0].keyframes.map((k) => k.t)).toEqual([0, 1]);
161
+ expect(tl.tracks[1].keyframes.map((k) => k.t)).toEqual([1, 3]); // shifted by first dur
162
+ });
163
+
164
+ test('sequence honors a per-timeline begin gap', () => {
165
+ const tl = sequence(
166
+ timeline({ tracks: [a()], dur: 1 }),
167
+ timeline({ tracks: [a()], dur: 1, begin: 0.5 })
168
+ );
169
+ expect(tl.tracks[1].keyframes[0].t).toBe(1.5); // 1 (cursor) + 0.5 (gap)
170
+ expect(tl.dur).toBe(2.5);
171
+ });
172
+ });
@@ -0,0 +1,107 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { sampleTrack } from '../animate.ts';
3
+ import { blobPath } from '../geometry.ts';
4
+ import { morphVariants, parseMorphPath, templateToPath } from '../morph.ts';
5
+ import type { Point } from '../primitives.ts';
6
+
7
+ describe('parseMorphPath', () => {
8
+ test('extracts vertices + a command template (M…C…Z)', () => {
9
+ const { template, vertices } = parseMorphPath('M0 0C1 1 2 2 3 3Z');
10
+ expect(template.commands.map((c) => c.cmd)).toEqual(['M', 'C', 'Z']);
11
+ expect(template.vertexCount).toBe(4); // M=1 + C=3
12
+ expect(vertices).toEqual([
13
+ { x: 0, y: 0 },
14
+ { x: 1, y: 1 },
15
+ { x: 2, y: 2 },
16
+ { x: 3, y: 3 },
17
+ ]);
18
+ });
19
+ test('round-trips through templateToPath', () => {
20
+ const d = 'M0 0L10 0L10 10L0 10Z';
21
+ const { template, vertices } = parseMorphPath(d);
22
+ expect(templateToPath(template, vertices)).toBe(d);
23
+ });
24
+ test('parses negative + decimal + exponent numbers', () => {
25
+ const { vertices } = parseMorphPath('M-1.5 2e1L3 -4.25');
26
+ expect(vertices).toEqual([
27
+ { x: -1.5, y: 20 },
28
+ { x: 3, y: -4.25 },
29
+ ]);
30
+ });
31
+ test('rejects non-morph-safe commands (H/V/A)', () => {
32
+ expect(() => parseMorphPath('M0 0H10')).toThrow(/morph-safe/);
33
+ expect(() => parseMorphPath('M0 0A5 5 0 0 1 10 10')).toThrow(/morph-safe/);
34
+ });
35
+ test('rejects relative (lowercase) commands', () => {
36
+ expect(() => parseMorphPath('M0 0l10 10')).toThrow(/morph-safe/);
37
+ });
38
+ test('templateToPath enforces the vertex-count invariant', () => {
39
+ const { template } = parseMorphPath('M0 0L1 1');
40
+ expect(() => templateToPath(template, [{ x: 0, y: 0 }])).toThrow(/vertex count/);
41
+ });
42
+ });
43
+
44
+ describe('morphVariants', () => {
45
+ const base = blobPath(50, 50, 30, { lobes: 7, seed: 3 });
46
+
47
+ test('every keyframe shares the fixed vertex count (interpolability)', () => {
48
+ const { track, template } = morphVariants(base, { n: 4, jitter: 3, seed: 7 });
49
+ for (const kf of track.keyframes) {
50
+ expect((kf.value as Point[]).length).toBe(template.vertexCount);
51
+ }
52
+ });
53
+
54
+ test('is deterministic for a given seed', () => {
55
+ const a = morphVariants(base, { n: 3, jitter: 4, seed: 9 });
56
+ const b = morphVariants(base, { n: 3, jitter: 4, seed: 9 });
57
+ expect(a.track.keyframes).toEqual(b.track.keyframes);
58
+ });
59
+
60
+ test('different seeds produce different jitter', () => {
61
+ const a = morphVariants(base, { n: 3, jitter: 4, seed: 1 });
62
+ const b = morphVariants(base, { n: 3, jitter: 4, seed: 2 });
63
+ expect(a.track.keyframes[1].value).not.toEqual(b.track.keyframes[1].value);
64
+ });
65
+
66
+ test('jitter 0 keeps every keyframe equal to the base shape', () => {
67
+ const { track } = morphVariants(base, { n: 3, jitter: 0, seed: 5 });
68
+ const first = track.keyframes[0].value;
69
+ for (const kf of track.keyframes) expect(kf.value).toEqual(first);
70
+ });
71
+
72
+ test('loop (default) returns to the base on the final keyframe', () => {
73
+ const { track } = morphVariants(base, { n: 3, jitter: 5, seed: 5 });
74
+ const kfs = track.keyframes;
75
+ expect(kfs[kfs.length - 1].value).toEqual(kfs[0].value); // seamless repeat
76
+ });
77
+
78
+ test('loop:false drops the closing base keyframe', () => {
79
+ const loop = morphVariants(base, { n: 3, jitter: 5, loop: true });
80
+ const open = morphVariants(base, { n: 3, jitter: 5, loop: false });
81
+ expect(open.track.keyframes.length).toBe(loop.track.keyframes.length - 1);
82
+ });
83
+
84
+ test('keyframes span [0, dur] evenly', () => {
85
+ const { track } = morphVariants(base, { n: 2, dur: 2 });
86
+ const ts = track.keyframes.map((k) => k.t);
87
+ expect(ts[0]).toBe(0);
88
+ expect(ts[ts.length - 1]).toBe(2);
89
+ });
90
+
91
+ test('sampling + toPath yields a valid morph-safe d at mid-timeline', () => {
92
+ const { track, toPath } = morphVariants(base, { n: 3, jitter: 4, dur: 1, seed: 2 });
93
+ const mid = sampleTrack(track, 0.5) as Point[];
94
+ const d = toPath(mid);
95
+ expect(d.startsWith('M')).toBe(true);
96
+ // Re-parsing the rendered frame must keep the same vertex count.
97
+ expect(() => parseMorphPath(d)).not.toThrow();
98
+ });
99
+
100
+ test('ease is applied to all but the last keyframe', () => {
101
+ const ease = [0.34, 1.42, 0.64, 1] as const;
102
+ const { track } = morphVariants(base, { n: 2, ease });
103
+ const kfs = track.keyframes;
104
+ for (let i = 0; i < kfs.length - 1; i++) expect(kfs[i].ease).toEqual(ease);
105
+ expect(kfs[kfs.length - 1].ease).toBeUndefined();
106
+ });
107
+ });
@@ -0,0 +1,142 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { parallel, type Timeline, timeline, track } from '../animate.ts';
3
+ import { morphVariants } from '../morph.ts';
4
+ import { circle, type DrawPrimitive, path } from '../primitives.ts';
5
+ import { buildAnimPlan, toAnimatedJsx, toAnimatedSvg } from '../serialize-animate.ts';
6
+
7
+ // An animated mark: a morphing blob + a blinking eye + a rotating blob.
8
+ const blob = morphVariants('M10 50L50 10L90 50L50 90Z', {
9
+ n: 3,
10
+ jitter: 5,
11
+ seed: 4,
12
+ dur: 1.2,
13
+ ease: [0.34, 1.42, 0.64, 1], // snap with overshoot
14
+ target: 'blob',
15
+ });
16
+
17
+ const PRIMS: DrawPrimitive[] = [
18
+ path({ d: 'M10 50L50 10L90 50L50 90Z', id: 'blob', fill: '#e94' }),
19
+ circle({ cx: 50, cy: 45, r: 6, id: 'eye', fill: '#222' }),
20
+ ];
21
+
22
+ const TL: Timeline = parallel(
23
+ timeline({ tracks: [blob.track], repeat: 'indefinite' }),
24
+ timeline({
25
+ tracks: [
26
+ track(
27
+ 'opacity',
28
+ [
29
+ { t: 0, value: 1, ease: [0.42, 0, 0.58, 1] },
30
+ { t: 0.5, value: 0.1 },
31
+ { t: 1, value: 1 },
32
+ ],
33
+ 'eye'
34
+ ),
35
+ track(
36
+ 'rotate',
37
+ [
38
+ { t: 0, value: 0 },
39
+ { t: 1.2, value: 360 },
40
+ ],
41
+ 'blob'
42
+ ),
43
+ ],
44
+ repeat: 'indefinite',
45
+ })
46
+ );
47
+
48
+ const OPTS = { viewBox: '0 0 100 100', pathRenderers: { blob: blob.toPath } };
49
+
50
+ describe('buildAnimPlan', () => {
51
+ const plan = buildAnimPlan(TL, OPTS);
52
+ test('one entry per targeted, ≥2-keyframe track', () => {
53
+ expect(plan.length).toBe(3);
54
+ });
55
+ test('d-morph maps to <animate attributeName=d>', () => {
56
+ const d = plan.find((e) => e.property === 'd');
57
+ expect(d?.kind).toBe('animate');
58
+ expect(d?.attr).toBe('d');
59
+ expect(d?.values[0].startsWith('M')).toBe(true);
60
+ });
61
+ test('rotate maps to animateTransform', () => {
62
+ const r = plan.find((e) => e.property === 'rotate');
63
+ expect(r?.kind).toBe('animateTransform');
64
+ expect(r?.attr).toBe('rotate');
65
+ });
66
+ test('keyTimes are normalized 0→1', () => {
67
+ for (const e of plan) {
68
+ expect(e.keyTimes[0]).toBe(0);
69
+ expect(e.keyTimes[e.keyTimes.length - 1]).toBe(1);
70
+ }
71
+ });
72
+ test('eased track carries n-1 keySplines', () => {
73
+ const op = plan.find((e) => e.property === 'opacity');
74
+ expect(op).toBeDefined();
75
+ expect(op?.keySplines?.length).toBe((op?.values.length ?? 0) - 1);
76
+ });
77
+ });
78
+
79
+ describe('toAnimatedSvg (SMIL)', () => {
80
+ const svg = toAnimatedSvg(PRIMS, TL, OPTS);
81
+ test('injects SMIL d-morph with spline easing', () => {
82
+ expect(svg).toContain('<animate attributeName="d"');
83
+ expect(svg).toContain('calcMode="spline"');
84
+ expect(svg).toContain('keySplines=');
85
+ });
86
+ test('rotate is an additive animateTransform (no static-transform clobber)', () => {
87
+ expect(svg).toMatch(/<animateTransform[^>]*type="rotate"[^>]*additive="sum"/);
88
+ });
89
+ test('preserves the overshoot handle verbatim in keySplines', () => {
90
+ expect(svg).toContain('0.34 1.42 0.64 1');
91
+ });
92
+ test('emits a prefers-reduced-motion gate', () => {
93
+ expect(svg).toContain('prefers-reduced-motion: reduce');
94
+ });
95
+ test('is a complete SVG document', () => {
96
+ expect(svg).toContain('xmlns="http://www.w3.org/2000/svg"');
97
+ expect(svg).toContain('repeatCount="indefinite"');
98
+ });
99
+ });
100
+
101
+ describe('toAnimatedJsx (single-source parity)', () => {
102
+ const svg = toAnimatedSvg(PRIMS, TL, OPTS);
103
+ const jsx = toAnimatedJsx(PRIMS, TL, OPTS);
104
+ const count = (s: string, re: RegExp) => (s.match(re) ?? []).length;
105
+
106
+ test('same count of <animate> elements as the SVG form', () => {
107
+ expect(count(jsx, /<animate /g)).toBe(count(svg, /<animate /g));
108
+ });
109
+ test('same count of <animateTransform> elements as the SVG form', () => {
110
+ expect(count(jsx, /<animateTransform /g)).toBe(count(svg, /<animateTransform /g));
111
+ });
112
+ test('omits the xmlns document marker', () => {
113
+ expect(jsx).not.toContain('xmlns=');
114
+ });
115
+ test('keeps the SMIL animation attributes (host renders SMIL)', () => {
116
+ expect(jsx).toContain('attributeName="d"');
117
+ expect(jsx).toContain('keyTimes=');
118
+ });
119
+ });
120
+
121
+ describe('fail-loud', () => {
122
+ test('a track targeting a missing id throws', () => {
123
+ const bad = timeline({
124
+ tracks: [
125
+ track(
126
+ 'opacity',
127
+ [
128
+ { t: 0, value: 0 },
129
+ { t: 1, value: 1 },
130
+ ],
131
+ 'ghost'
132
+ ),
133
+ ],
134
+ repeat: 1,
135
+ });
136
+ expect(() => toAnimatedSvg(PRIMS, bad, { viewBox: '0 0 100 100' })).toThrow(/not found/);
137
+ });
138
+ test('a d-morph track without a pathRenderer throws', () => {
139
+ const tl = timeline({ tracks: [blob.track], repeat: 1 });
140
+ expect(() => toAnimatedSvg(PRIMS, tl, { viewBox: '0 0 100 100' })).toThrow(/pathRenderer/);
141
+ });
142
+ });
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file equal-spacing-detector.ts — T27 (Wave 3)
3
- * @scope plugins/design/dev-server/equal-spacing-detector.ts
3
+ * @scope apps/studio/equal-spacing-detector.ts
4
4
  * @purpose Pure detector for Figma "Smart Selection" pink-dot affordance
5
5
  * (Rasmus Andersson 2018). Given 3+ rects on a single axis,
6
6
  * returns the equal gap + the screen-coord midpoints between
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file equal-spacing-handles.tsx — T27 (Wave 3)
3
- * @scope plugins/design/dev-server/equal-spacing-handles.tsx
3
+ * @scope apps/studio/equal-spacing-handles.tsx
4
4
  * @purpose Figma Smart Selection pink-dot affordance. When 3+ elements are
5
5
  * selected AND their bounding rects are equally distributed on an
6
6
  * axis, render small pink dots at the midpoints between adjacent
@@ -14,11 +14,8 @@
14
14
 
15
15
  import { useEffect, useRef, useState } from 'react';
16
16
 
17
- import {
18
- detectEqualSpacing,
19
- type EqualSpacingResult,
20
- type SpacingAxis,
21
- } from './equal-spacing-detector.ts';
17
+ import { resolveSelectionEl } from './dom-selection.ts';
18
+ import { detectEqualSpacing, type EqualSpacingResult } from './equal-spacing-detector.ts';
22
19
  import { useSelectionSet } from './use-selection-set.tsx';
23
20
 
24
21
  const HOVER_PADDING_PX = 40;
@@ -77,25 +74,6 @@ function ensureStyles(): void {
77
74
  document.head.appendChild(s);
78
75
  }
79
76
 
80
- function cssEscape(s: string): string {
81
- // CSS.escape is available everywhere we care; manual fallback handles the
82
- // jsdom test path where it may be missing.
83
- // biome-ignore lint/suspicious/noExplicitAny: feature detect
84
- const ce = (typeof CSS !== 'undefined' ? (CSS as any).escape : null) as
85
- | ((v: string) => string)
86
- | null;
87
- if (ce) return ce(s);
88
- return s.replace(/(["\\\]])/g, '\\$1');
89
- }
90
-
91
- function safeQuery(selector: string): Element | null {
92
- try {
93
- return document.querySelector(selector);
94
- } catch {
95
- return null;
96
- }
97
- }
98
-
99
77
  interface ScreenRect {
100
78
  x: number;
101
79
  y: number;
@@ -103,12 +81,12 @@ interface ScreenRect {
103
81
  h: number;
104
82
  }
105
83
 
106
- function resolveSelectionRects(sels: Array<{ id?: string; selector: string }>): ScreenRect[] {
84
+ function resolveSelectionRects(
85
+ sels: Array<{ id?: string; selector: string; artboardId?: string | null }>
86
+ ): ScreenRect[] {
107
87
  const rects: ScreenRect[] = [];
108
88
  for (const s of sels) {
109
- const el = s.id
110
- ? document.querySelector(`[data-cd-id="${cssEscape(s.id)}"]`)
111
- : safeQuery(s.selector);
89
+ const el = resolveSelectionEl(document, s);
112
90
  if (!el) continue;
113
91
  const b = (el as HTMLElement).getBoundingClientRect();
114
92
  if (b.width === 0 && b.height === 0) continue;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file export-dialog.tsx — Phase 6.5 T8 export dialog.
3
- * @scope plugins/design/dev-server/export-dialog.tsx
3
+ * @scope apps/studio/export-dialog.tsx
4
4
  * @purpose Native `<dialog>`-based export modal. Three controls — format,
5
5
  * scope, per-format options — plus a Recent tab populated by
6
6
  * `/_api/export-history`. Submit fires `POST /_api/export`, the
@@ -321,6 +321,7 @@ export function ExportDialogProvider({ children }: { children: ReactNode }): Rea
321
321
  }, [openState, loadHistory]);
322
322
 
323
323
  // ⌘E / Ctrl+E to open; ⌘⇧E / Ctrl+Shift+E to re-run last.
324
+ // biome-ignore lint/correctness/useExhaustiveDependencies: re-bind on `open` only — rerunLast is stable per render; re-running on it would tear down the global hotkey listener needlessly.
324
325
  useEffect(() => {
325
326
  function onKey(e: KeyboardEvent) {
326
327
  const mod = e.metaKey || e.ctrlKey;
@@ -331,7 +332,6 @@ export function ExportDialogProvider({ children }: { children: ReactNode }): Rea
331
332
  }
332
333
  window.addEventListener('keydown', onKey);
333
334
  return () => window.removeEventListener('keydown', onKey);
334
- // eslint-disable-next-line react-hooks/exhaustive-deps
335
335
  }, [open]);
336
336
 
337
337
  // Phase 6.5 T9 — context-menu entries dispatch `maude:open-export` so they
@@ -339,6 +339,18 @@ export function ExportDialogProvider({ children }: { children: ReactNode }): Rea
339
339
  useEffect(() => {
340
340
  function onCustom(e: Event) {
341
341
  const detail = (e as CustomEvent<{ scope?: Scope; format?: Format }>).detail ?? {};
342
+ // Plan C — route to the UNIFIED shell Export dialog (parent / main origin)
343
+ // so the in-canvas toolbar + context menu open the SAME modal as the
344
+ // menubar. Only fall back to this local dialog for a standalone canvas
345
+ // (handoff / exported embed) with no shell parent.
346
+ if (window.parent && window.parent !== window) {
347
+ try {
348
+ window.parent.postMessage({ dgn: 'open-export', detail }, '*');
349
+ return;
350
+ } catch {
351
+ /* fall through to local */
352
+ }
353
+ }
342
354
  open(detail);
343
355
  }
344
356
  window.addEventListener('maude:open-export', onCustom as EventListener);
@@ -547,8 +559,8 @@ const DialogShell = (() => {
547
559
  <div className="dc-ed-recent">
548
560
  <h3>Recent</h3>
549
561
  <ul>
550
- {history.slice(0, 5).map((h, i) => (
551
- <li key={`${h.at}-${i}`}>
562
+ {history.slice(0, 5).map((h) => (
563
+ <li key={`${h.at}-${h.filename}`}>
552
564
  <button
553
565
  type="button"
554
566
  onClick={() => {
@@ -5,6 +5,7 @@
5
5
  // from the WS layer and from a future server-driven auto-snapshot hook.
6
6
 
7
7
  import type { Dirent } from 'node:fs';
8
+ import { existsSync } from 'node:fs';
8
9
  import { readdir } from 'node:fs/promises';
9
10
  import path from 'node:path';
10
11
 
@@ -37,10 +38,14 @@ function fileSlug(file: string, designRel: string): string {
37
38
  }
38
39
  const prefix = `${designRel.replace(/^\/+|\/+$/g, '')}/`;
39
40
  if (p.startsWith(prefix)) p = p.slice(prefix.length);
41
+ // Extension strip mirrors bin/slug.sh (the canonical `_history/<slug>/`
42
+ // recipe) — previously only `.html` was stripped, so `.tsx` canvases got a
43
+ // `ui-foo.tsx` history dir that /design:rollback (slug.sh: `ui-foo`) could
44
+ // never find. DDR-102 conflict snapshots rely on the dirs matching.
40
45
  return p
41
46
  .replace(/\//g, '-')
42
47
  .replace(/\s+/g, '_')
43
- .replace(/\.html$/i, '')
48
+ .replace(/\.(tsx|jsx|html?|css|json|md)$/i, '')
44
49
  .replace(/^\.+/, '')
45
50
  .toLowerCase();
46
51
  }
@@ -60,8 +65,20 @@ export function createHistory(ctx: Context): History {
60
65
 
61
66
  async function writeSnapshot(file: string, contentBytes: Uint8Array | string, reason: string) {
62
67
  const slug = fileSlug(file, ctx.paths.designRel);
63
- const ts = new Date().toISOString();
64
- const contentPath = snapshotPath(slug, ts);
68
+ // Snapshot blob keeps the source file's extension (a `.tsx` body snapshot
69
+ // is a `.tsx` blob); unknown/absent extension falls back to `.html`.
70
+ const extMatch = /\.(tsx|jsx|html?|css|json|svg|md)$/i.exec(file);
71
+ const ext = extMatch ? extMatch[0].toLowerCase() : '.html';
72
+ // Two snapshots can land within the same millisecond (the DDR-102 dual
73
+ // pre-sync pair does) — toISOString() would collide and the second blob
74
+ // would overwrite the first. Bump by 1 ms until the slot is free.
75
+ let tsMs = Date.now();
76
+ let ts = new Date(tsMs).toISOString();
77
+ while (existsSync(snapshotPath(slug, ts, ext)) || existsSync(metaPath(slug, ts))) {
78
+ tsMs += 1;
79
+ ts = new Date(tsMs).toISOString();
80
+ }
81
+ const contentPath = snapshotPath(slug, ts, ext);
65
82
  const meta: Snapshot = {
66
83
  slug,
67
84
  ts,