@1agh/maude 0.28.0 → 0.29.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 (361) hide show
  1. package/README.md +2 -2
  2. package/{plugins/design/dev-server → apps/studio}/ai-banner.tsx +1 -1
  3. package/{plugins/design/dev-server → apps/studio}/annotations-context-toolbar.tsx +286 -6
  4. package/{plugins/design/dev-server → apps/studio}/annotations-layer.tsx +1209 -122
  5. package/{plugins/design/dev-server → apps/studio}/api.ts +486 -32
  6. package/{plugins/design/dev-server → apps/studio}/artboard-activity-overlay.tsx +1 -1
  7. package/{plugins/design/dev-server → apps/studio}/artboard-marquee.tsx +1 -1
  8. package/apps/studio/bin/_motion-sample-playwright.mjs +129 -0
  9. package/{plugins/design/dev-server → apps/studio}/bin/canvas-edit.sh +1 -1
  10. package/{plugins/design/dev-server → apps/studio}/bin/draw-proof.sh +37 -2
  11. package/{plugins/design/dev-server → apps/studio}/bin/preflight.sh +1 -1
  12. package/apps/studio/bin/read-annotations.mjs +438 -0
  13. package/apps/studio/bin/read-annotations.sh +11 -0
  14. package/{plugins/design/dev-server → apps/studio}/bin/runtime-health.sh +1 -1
  15. package/{plugins/design/dev-server → apps/studio}/bin/scenario-report.mjs +1 -1
  16. package/{plugins/design/dev-server → apps/studio}/bin/screenshot.sh +62 -2
  17. package/apps/studio/bin/server-up.sh +234 -0
  18. package/apps/studio/bin/to-lottie-verify.html +68 -0
  19. package/apps/studio/bin/to-lottie.sh +128 -0
  20. package/{plugins/design/dev-server → apps/studio}/bin/visual-sanity.sh +19 -3
  21. package/{plugins/design/dev-server → apps/studio}/build.ts +5 -3
  22. package/{plugins/design/dev-server → apps/studio}/canvas-arrowheads.ts +1 -1
  23. package/{plugins/design/dev-server → apps/studio}/canvas-comment-mount.tsx +2 -2
  24. package/apps/studio/canvas-create.ts +104 -0
  25. package/{plugins/design/dev-server → apps/studio}/canvas-cursors.ts +47 -13
  26. package/{plugins/design/dev-server → apps/studio}/canvas-icons.tsx +60 -1
  27. package/{plugins/design/dev-server → apps/studio}/canvas-lib-inline.ts +1 -1
  28. package/{plugins/design/dev-server → apps/studio}/canvas-lib-resolver.ts +1 -1
  29. package/{plugins/design/dev-server → apps/studio}/canvas-lib.tsx +41 -33
  30. package/{plugins/design/dev-server → apps/studio}/canvas-shell.tsx +57 -42
  31. package/{plugins/design/dev-server → apps/studio}/client/app.jsx +1713 -530
  32. package/apps/studio/client/canvas-url.js +81 -0
  33. package/{plugins/design/dev-server → apps/studio}/client/comments-overlay.css +41 -27
  34. package/{plugins/design/dev-server → apps/studio}/client/index.html +2 -1
  35. package/apps/studio/client/styles/1-tokens-maude.css +296 -0
  36. package/apps/studio/client/styles/3-shell-maude.css +459 -0
  37. package/{plugins/design/dev-server → apps/studio}/client/styles/3-shell.css +129 -0
  38. package/apps/studio/client/styles/4-components-maude.css +74 -0
  39. package/{plugins/design/dev-server → apps/studio}/client/styles/4-components.css +357 -34
  40. package/apps/studio/client/styles/5-maude-overrides.css +123 -0
  41. package/{plugins/design/dev-server → apps/studio}/client/styles/_index.css +9 -0
  42. package/{plugins/design/dev-server → apps/studio}/client/styles.css +1 -1
  43. package/apps/studio/client/tour/overlay.jsx +191 -0
  44. package/apps/studio/client/tour/usage-tour.js +35 -0
  45. package/apps/studio/client/whats-new-seen.js +52 -0
  46. package/apps/studio/client/whats-new.jsx +250 -0
  47. package/{plugins/design/dev-server → apps/studio}/collab/index.ts +4 -4
  48. package/{plugins/design/dev-server → apps/studio}/collab/persistence.ts +1 -1
  49. package/{plugins/design/dev-server → apps/studio}/commands/annotation-strokes-command.ts +1 -1
  50. package/{plugins/design/dev-server → apps/studio}/commands/equal-spacing-command.ts +1 -1
  51. package/{plugins/design/dev-server → apps/studio}/commands/move-artboards-command.ts +1 -1
  52. package/{plugins/design/dev-server → apps/studio}/comments-overlay.tsx +25 -6
  53. package/{plugins/design/dev-server → apps/studio}/context-menu.tsx +2 -2
  54. package/{plugins/design/dev-server → apps/studio}/contextual-toolbar.tsx +4 -7
  55. package/{plugins/design/dev-server → apps/studio}/cursors-overlay.tsx +1 -1
  56. package/apps/studio/dist/client.bundle.js +22825 -0
  57. package/apps/studio/dist/comment-mount.js +1960 -0
  58. package/apps/studio/dist/runtime/lib0_decoding.js +1 -0
  59. package/apps/studio/dist/runtime/lib0_encoding.js +1 -0
  60. package/apps/studio/dist/runtime/motion.js +4 -0
  61. package/apps/studio/dist/runtime/motion_react.js +12 -0
  62. package/apps/studio/dist/runtime/pixi-js.js +3397 -0
  63. package/apps/studio/dist/runtime/react-dom.js +1 -0
  64. package/apps/studio/dist/runtime/react-dom_client.js +9 -0
  65. package/apps/studio/dist/runtime/react.js +1 -0
  66. package/apps/studio/dist/runtime/react_jsx-dev-runtime.js +1 -0
  67. package/apps/studio/dist/runtime/react_jsx-runtime.js +1 -0
  68. package/apps/studio/dist/runtime/y-protocols_awareness.js +1 -0
  69. package/apps/studio/dist/runtime/y-protocols_sync.js +1 -0
  70. package/apps/studio/dist/runtime/yjs.js +5 -0
  71. package/apps/studio/dist/styles.css +5695 -0
  72. package/{plugins/design/dev-server → apps/studio}/dom-selection.ts +75 -4
  73. package/apps/studio/draw/animate.ts +333 -0
  74. package/{plugins/design/dev-server → apps/studio}/draw/brush.ts +4 -4
  75. package/{plugins/design/dev-server → apps/studio}/draw/composition.ts +1 -1
  76. package/{plugins/design/dev-server → apps/studio}/draw/geometry.ts +1 -1
  77. package/{plugins/design/dev-server → apps/studio}/draw/index.ts +9 -6
  78. package/{plugins/design/dev-server → apps/studio}/draw/layout.ts +2 -2
  79. package/apps/studio/draw/morph.ts +204 -0
  80. package/{plugins/design/dev-server → apps/studio}/draw/optimize.ts +1 -1
  81. package/{plugins/design/dev-server → apps/studio}/draw/palette.ts +1 -1
  82. package/{plugins/design/dev-server → apps/studio}/draw/primitives.ts +1 -1
  83. package/apps/studio/draw/serialize-animate.ts +324 -0
  84. package/{plugins/design/dev-server → apps/studio}/draw/serialize.ts +9 -3
  85. package/apps/studio/draw/test/animate.test.ts +172 -0
  86. package/{plugins/design/dev-server → apps/studio}/draw/test/layout.test.ts +1 -1
  87. package/apps/studio/draw/test/morph.test.ts +107 -0
  88. package/apps/studio/draw/test/serialize-animate.test.ts +142 -0
  89. package/{plugins/design/dev-server → apps/studio}/draw/test/serialize.test.ts +1 -1
  90. package/{plugins/design/dev-server → apps/studio}/equal-spacing-detector.ts +1 -1
  91. package/{plugins/design/dev-server → apps/studio}/equal-spacing-handles.tsx +7 -29
  92. package/{plugins/design/dev-server → apps/studio}/examples/perf-100-artboards.tsx +1 -2
  93. package/{plugins/design/dev-server → apps/studio}/export-dialog.tsx +14 -2
  94. package/{plugins/design/dev-server → apps/studio}/exporters/index.ts +2 -2
  95. package/{plugins/design/dev-server → apps/studio}/http.ts +103 -2
  96. package/{plugins/design/dev-server → apps/studio}/input-router.tsx +8 -1
  97. package/{plugins/design/dev-server → apps/studio}/inspect.ts +20 -3
  98. package/{plugins/design/dev-server → apps/studio}/marquee-overlay.tsx +6 -3
  99. package/{plugins/design/dev-server → apps/studio}/participants-chrome.tsx +1 -1
  100. package/{plugins/design/dev-server → apps/studio}/paths.ts +15 -11
  101. package/{plugins/design/dev-server → apps/studio}/server.ts +19 -1
  102. package/{plugins/design/dev-server → apps/studio}/sync/index.ts +6 -6
  103. package/{plugins/design/dev-server → apps/studio}/sync/materialize.ts +2 -2
  104. package/{plugins/design/dev-server → apps/studio}/test/ai-activity.test.ts +1 -1
  105. package/{plugins/design/dev-server → apps/studio}/test/annotations-api.test.ts +1 -2
  106. package/{plugins/design/dev-server → apps/studio}/test/annotations-layer.test.ts +69 -6
  107. package/{plugins/design/dev-server → apps/studio}/test/annotations-roundtrip.test.ts +98 -1
  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/{plugins/design/dev-server → apps/studio}/test/boot-self-heal.test.ts +1 -1
  111. package/apps/studio/test/canvas-create-api.test.ts +361 -0
  112. package/{plugins/design/dev-server → apps/studio}/test/canvas-cursors.test.ts +3 -1
  113. package/{plugins/design/dev-server → apps/studio}/test/canvas-edit.test.ts +1 -1
  114. package/{plugins/design/dev-server → apps/studio}/test/canvas-hmr-runtime.test.tsx +1 -1
  115. package/{plugins/design/dev-server → apps/studio}/test/canvas-lib-inline.test.ts +1 -1
  116. package/{plugins/design/dev-server → apps/studio}/test/canvas-lib-resolver.test.ts +2 -3
  117. package/apps/studio/test/canvas-media-drop.test.ts +158 -0
  118. package/{plugins/design/dev-server → apps/studio}/test/canvas-meta-api.test.ts +1 -2
  119. package/{plugins/design/dev-server → apps/studio}/test/canvas-origin-gate.test.ts +18 -3
  120. package/apps/studio/test/canvas-url.test.ts +99 -0
  121. package/{plugins/design/dev-server → apps/studio}/test/collab-annotations-bridge.test.ts +1 -1
  122. package/{plugins/design/dev-server → apps/studio}/test/collab-bridge.test.ts +1 -1
  123. package/{plugins/design/dev-server → apps/studio}/test/collab-room.test.ts +1 -1
  124. package/{plugins/design/dev-server → apps/studio}/test/collab-stress.test.ts +1 -1
  125. package/{plugins/design/dev-server → apps/studio}/test/comments-api.test.ts +1 -2
  126. package/{plugins/design/dev-server → apps/studio}/test/compile-entry.test.ts +1 -2
  127. package/{plugins/design/dev-server → apps/studio}/test/exporters/canva.test.ts +1 -2
  128. package/{plugins/design/dev-server → apps/studio}/test/exporters/history.test.ts +1 -2
  129. package/{plugins/design/dev-server → apps/studio}/test/exporters/pw-launch.test.ts +1 -1
  130. package/{plugins/design/dev-server → apps/studio}/test/exporters/scope.test.ts +1 -2
  131. package/{plugins/design/dev-server → apps/studio}/test/exporters/zip.test.ts +1 -2
  132. package/{plugins/design/dev-server → apps/studio}/test/git-lifecycle.test.ts +1 -2
  133. package/{plugins/design/dev-server → apps/studio}/test/handoff-static-frames.test.ts +1 -1
  134. package/{plugins/design/dev-server → apps/studio}/test/hmr-broadcast.test.ts +1 -1
  135. package/apps/studio/test/index-data-canvas-ds.test.ts +97 -0
  136. package/apps/studio/test/motion-sample.test.ts +35 -0
  137. package/{plugins/design/dev-server → apps/studio}/test/perf-harness.ts +1 -1
  138. package/{plugins/design/dev-server → apps/studio}/test/phase-3.6-smoke.test.ts +3 -1
  139. package/apps/studio/test/read-annotations.test.ts +409 -0
  140. package/{plugins/design/dev-server → apps/studio}/test/runtime-bundle.test.ts +1 -1
  141. package/{plugins/design/dev-server → apps/studio}/test/sanitize-annotation-svg.test.ts +108 -2
  142. package/{plugins/design/dev-server → apps/studio}/test/shared-doc-convergence.test.ts +2 -2
  143. package/{plugins/design/dev-server → apps/studio}/test/shared-doc-migrate.test.ts +2 -3
  144. package/{plugins/design/dev-server → apps/studio}/test/shared-doc-projection.test.ts +1 -1
  145. package/{plugins/design/dev-server → apps/studio}/test/snap-distance-pill.test.ts +1 -1
  146. package/{plugins/design/dev-server → apps/studio}/test/sync-agent.test.ts +2 -3
  147. package/{plugins/design/dev-server → apps/studio}/test/sync-atomic-write.test.ts +2 -3
  148. package/{plugins/design/dev-server → apps/studio}/test/sync-connection-state.test.ts +1 -1
  149. package/{plugins/design/dev-server → apps/studio}/test/sync-echo-guard.test.ts +1 -1
  150. package/{plugins/design/dev-server → apps/studio}/test/sync-fs-mirror.test.ts +1 -2
  151. package/{plugins/design/dev-server → apps/studio}/test/sync-hardening.test.ts +4 -5
  152. package/{plugins/design/dev-server → apps/studio}/test/sync-hubs-config.test.ts +1 -2
  153. package/{plugins/design/dev-server → apps/studio}/test/sync-runtime.test.ts +3 -8
  154. package/{plugins/design/dev-server → apps/studio}/test/sync-status.test.ts +1 -1
  155. package/{plugins/design/dev-server → apps/studio}/test/sync-untrusted.test.ts +1 -2
  156. package/apps/studio/test/tour-overlay.test.tsx +56 -0
  157. package/{plugins/design/dev-server → apps/studio}/test/use-agent-presence.test.tsx +4 -1
  158. package/{plugins/design/dev-server → apps/studio}/test/use-annotation-resize.test.ts +43 -0
  159. package/{plugins/design/dev-server → apps/studio}/test/use-snap-guides.test.ts +1 -1
  160. package/{plugins/design/dev-server → apps/studio}/test/use-tool-mode.test.tsx +4 -1
  161. package/apps/studio/test/whats-new.test.ts +101 -0
  162. package/apps/studio/text-imports.d.ts +9 -0
  163. package/{plugins/design/dev-server → apps/studio}/tool-palette.tsx +4 -69
  164. package/{plugins/design/dev-server → apps/studio}/undo-hud.tsx +1 -1
  165. package/{plugins/design/dev-server → apps/studio}/undo-stack.ts +1 -1
  166. package/{plugins/design/dev-server → apps/studio}/use-agent-presence.tsx +2 -2
  167. package/{plugins/design/dev-server → apps/studio}/use-annotation-resize.tsx +41 -12
  168. package/{plugins/design/dev-server → apps/studio}/use-annotation-selection.tsx +3 -7
  169. package/{plugins/design/dev-server → apps/studio}/use-annotations-visibility.tsx +2 -2
  170. package/{plugins/design/dev-server → apps/studio}/use-artboard-drag.tsx +2 -2
  171. package/{plugins/design/dev-server → apps/studio}/use-canvas-activity.tsx +2 -2
  172. package/apps/studio/use-canvas-media-drop.tsx +343 -0
  173. package/{plugins/design/dev-server → apps/studio}/use-collab.tsx +4 -5
  174. package/{plugins/design/dev-server → apps/studio}/use-cursor-modifiers.tsx +1 -1
  175. package/{plugins/design/dev-server → apps/studio}/use-keyboard-discipline.tsx +5 -2
  176. package/{plugins/design/dev-server → apps/studio}/use-selection-set.tsx +10 -2
  177. package/{plugins/design/dev-server → apps/studio}/use-snap-guides.tsx +1 -1
  178. package/{plugins/design/dev-server → apps/studio}/use-tool-mode.tsx +4 -2
  179. package/{plugins/design/dev-server → apps/studio}/use-undo-stack.tsx +5 -6
  180. package/apps/studio/whats-new.json +139 -0
  181. package/apps/studio/whats-new.schema.json +70 -0
  182. package/apps/studio/whats-new.ts +87 -0
  183. package/cli/bin/maude.mjs +10 -0
  184. package/cli/commands/design.mjs +84 -10
  185. package/cli/commands/design.test.mjs +133 -1
  186. package/cli/commands/help.mjs +8 -2
  187. package/cli/commands/hub.mjs +3 -3
  188. package/cli/commands/hub.test.mjs +1 -1
  189. package/cli/commands/scenario-report.mjs +2 -9
  190. package/cli/lib/cache.mjs +1 -1
  191. package/cli/lib/config-lint.mjs +1 -1
  192. package/cli/lib/copy-tree.mjs +1 -1
  193. package/cli/lib/design-link.mjs +1 -1
  194. package/cli/lib/flow-design-integration.test.mjs +2 -2
  195. package/cli/lib/gitignore-block.mjs +1 -0
  196. package/cli/lib/gitignore-block.test.mjs +2 -2
  197. package/cli/lib/plugin-cli-reachability.test.mjs +8 -7
  198. package/cli/lib/preflight.mjs +1 -1
  199. package/cli/lib/stack-detect.mjs +1 -1
  200. package/package.json +16 -16
  201. package/plugins/design/templates/_shell.html +3 -3
  202. package/plugins/design/templates/brief-board.tsx.template +61 -0
  203. package/plugins/design/templates/canvas.tsx.template +1 -1
  204. package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +18 -3
  205. package/plugins/design/templates/design-system-inspiration/core/config.json.tpl +1 -1
  206. package/plugins/design/templates/design-system-inspiration/core/preview/_motion-readme.md.tpl +1 -1
  207. package/plugins/flow/.claude-plugin/config.schema.json +16 -0
  208. package/plugins/design/dev-server/bin/server-up.sh +0 -135
  209. package/plugins/design/dev-server/dist/client.bundle.js +0 -20692
  210. package/plugins/design/dev-server/dist/comment-mount.js +0 -1924
  211. package/plugins/design/dev-server/dist/runtime/lib0_decoding.js +0 -620
  212. package/plugins/design/dev-server/dist/runtime/lib0_encoding.js +0 -604
  213. package/plugins/design/dev-server/dist/runtime/motion.js +0 -4944
  214. package/plugins/design/dev-server/dist/runtime/motion_react.js +0 -10056
  215. package/plugins/design/dev-server/dist/runtime/pixi-js.js +0 -53104
  216. package/plugins/design/dev-server/dist/runtime/react-dom.js +0 -231
  217. package/plugins/design/dev-server/dist/runtime/react-dom_client.js +0 -10357
  218. package/plugins/design/dev-server/dist/runtime/react.js +0 -535
  219. package/plugins/design/dev-server/dist/runtime/react_jsx-dev-runtime.js +0 -60
  220. package/plugins/design/dev-server/dist/runtime/react_jsx-runtime.js +0 -85
  221. package/plugins/design/dev-server/dist/runtime/y-protocols_awareness.js +0 -376
  222. package/plugins/design/dev-server/dist/runtime/y-protocols_sync.js +0 -100
  223. package/plugins/design/dev-server/dist/runtime/yjs.js +0 -6687
  224. package/plugins/design/dev-server/dist/styles.css +0 -2710
  225. package/{plugins/design/dev-server → apps/studio}/activity.ts +0 -0
  226. package/{plugins/design/dev-server → apps/studio}/bin/_enumerate-artboards-playwright.mjs +0 -0
  227. package/{plugins/design/dev-server → apps/studio}/bin/_html-playwright.mjs +0 -0
  228. package/{plugins/design/dev-server → apps/studio}/bin/_pdf-playwright.mjs +0 -0
  229. package/{plugins/design/dev-server → apps/studio}/bin/_png-playwright.mjs +0 -0
  230. package/{plugins/design/dev-server → apps/studio}/bin/_pptx-playwright.mjs +0 -0
  231. package/{plugins/design/dev-server → apps/studio}/bin/_pw-launch.mjs +0 -0
  232. package/{plugins/design/dev-server → apps/studio}/bin/_screenshot-playwright.mjs +0 -0
  233. package/{plugins/design/dev-server → apps/studio}/bin/_svg-optimize.mjs +0 -0
  234. package/{plugins/design/dev-server → apps/studio}/bin/_svg-playwright.mjs +0 -0
  235. package/{plugins/design/dev-server → apps/studio}/bin/asset-sweep.sh +0 -0
  236. package/{plugins/design/dev-server → apps/studio}/bin/bootstrap-check.sh +0 -0
  237. package/{plugins/design/dev-server → apps/studio}/bin/check-runtime-bundles.sh +0 -0
  238. package/{plugins/design/dev-server → apps/studio}/bin/draw-build.sh +0 -0
  239. package/{plugins/design/dev-server → apps/studio}/bin/handoff.sh +0 -0
  240. package/{plugins/design/dev-server → apps/studio}/bin/prep.sh +0 -0
  241. package/{plugins/design/dev-server → apps/studio}/bin/slug.sh +0 -0
  242. package/{plugins/design/dev-server → apps/studio}/bin/smoke.sh +0 -0
  243. package/{plugins/design/dev-server → apps/studio}/bin/svg-optimize.sh +0 -0
  244. package/{plugins/design/dev-server → apps/studio}/boot-self-heal.ts +0 -0
  245. package/{plugins/design/dev-server → apps/studio}/canvas-build.ts +0 -0
  246. package/{plugins/design/dev-server → apps/studio}/canvas-edit.ts +0 -0
  247. package/{plugins/design/dev-server → apps/studio}/canvas-header.ts +0 -0
  248. package/{plugins/design/dev-server → apps/studio}/canvas-meta.schema.json +0 -0
  249. package/{plugins/design/dev-server → apps/studio}/canvas-pipeline.ts +0 -0
  250. package/{plugins/design/dev-server → apps/studio}/client/hmr.mjs +0 -0
  251. package/{plugins/design/dev-server → apps/studio}/client/iframe-lazy.mjs +0 -0
  252. package/{plugins/design/dev-server → apps/studio}/client/styles/0-reset.css +0 -0
  253. package/{plugins/design/dev-server → apps/studio}/client/styles/1-tokens.css +0 -0
  254. package/{plugins/design/dev-server → apps/studio}/client/styles/2-layout.css +0 -0
  255. package/{plugins/design/dev-server → apps/studio}/client/styles/5-utilities.css +0 -0
  256. package/{plugins/design/dev-server → apps/studio}/collab/ai-activity.ts +0 -0
  257. package/{plugins/design/dev-server → apps/studio}/collab/awareness-bridge.ts +1 -1
  258. package/{plugins/design/dev-server → apps/studio}/collab/git-lifecycle.ts +0 -0
  259. package/{plugins/design/dev-server → apps/studio}/collab/protocol.ts +0 -0
  260. package/{plugins/design/dev-server → apps/studio}/collab/registry.ts +0 -0
  261. package/{plugins/design/dev-server → apps/studio}/collab/room.ts +0 -0
  262. package/{plugins/design/dev-server → apps/studio}/config.schema.json +0 -0
  263. package/{plugins/design/dev-server → apps/studio}/context.ts +0 -0
  264. package/{plugins/design/dev-server → apps/studio}/dist/runtime/.min-sizes.json +0 -0
  265. package/{plugins/design/dev-server → apps/studio}/draw/test/brush.test.ts +0 -0
  266. package/{plugins/design/dev-server → apps/studio}/draw/test/composition.test.ts +0 -0
  267. package/{plugins/design/dev-server → apps/studio}/draw/test/geometry.test.ts +1 -1
  268. package/{plugins/design/dev-server → apps/studio}/draw/test/gradient.test.ts +0 -0
  269. package/{plugins/design/dev-server → apps/studio}/draw/test/optimize.test.ts +0 -0
  270. package/{plugins/design/dev-server → apps/studio}/draw/test/palette.test.ts +0 -0
  271. package/{plugins/design/dev-server → apps/studio}/draw/test/primitives.test.ts +1 -1
  272. package/{plugins/design/dev-server → apps/studio}/examples/README.md +0 -0
  273. package/{plugins/design/dev-server → apps/studio}/exporters/_browser-bundles.ts +0 -0
  274. package/{plugins/design/dev-server → apps/studio}/exporters/canva-handoff-prompt.ts +0 -0
  275. package/{plugins/design/dev-server → apps/studio}/exporters/canva.ts +0 -0
  276. package/{plugins/design/dev-server → apps/studio}/exporters/html.ts +1 -1
  277. package/{plugins/design/dev-server → apps/studio}/exporters/pdf.ts +1 -1
  278. package/{plugins/design/dev-server → apps/studio}/exporters/png.ts +1 -1
  279. package/{plugins/design/dev-server → apps/studio}/exporters/pptx.ts +1 -1
  280. package/{plugins/design/dev-server → apps/studio}/exporters/scope.ts +0 -0
  281. package/{plugins/design/dev-server → apps/studio}/exporters/svg.ts +1 -1
  282. package/{plugins/design/dev-server → apps/studio}/exporters/zip.ts +0 -0
  283. package/{plugins/design/dev-server → apps/studio}/fs-watch.ts +0 -0
  284. package/{plugins/design/dev-server → apps/studio}/handoff.ts +0 -0
  285. package/{plugins/design/dev-server → apps/studio}/history.ts +0 -0
  286. package/{plugins/design/dev-server → apps/studio}/hmr-broadcast.ts +0 -0
  287. package/{plugins/design/dev-server → apps/studio}/locator.ts +0 -0
  288. package/{plugins/design/dev-server → apps/studio}/mem.ts +0 -0
  289. package/{plugins/design/dev-server → apps/studio}/runtime-bundle.ts +0 -0
  290. package/{plugins/design/dev-server → apps/studio}/server.mjs +0 -0
  291. package/{plugins/design/dev-server → apps/studio}/sync/agent.ts +1 -1
  292. package/{plugins/design/dev-server → apps/studio}/sync/atomic-write.ts +0 -0
  293. package/{plugins/design/dev-server → apps/studio}/sync/codec.ts +0 -0
  294. package/{plugins/design/dev-server → apps/studio}/sync/connection-state.ts +0 -0
  295. package/{plugins/design/dev-server → apps/studio}/sync/echo-guard.ts +0 -0
  296. package/{plugins/design/dev-server → apps/studio}/sync/fs-mirror.ts +0 -0
  297. package/{plugins/design/dev-server → apps/studio}/sync/hubs-config.ts +0 -0
  298. package/{plugins/design/dev-server → apps/studio}/sync/migrate-seed.ts +1 -1
  299. package/{plugins/design/dev-server → apps/studio}/sync/origins.ts +0 -0
  300. package/{plugins/design/dev-server → apps/studio}/sync/projection.ts +3 -3
  301. package/{plugins/design/dev-server → apps/studio}/sync/status.ts +0 -0
  302. package/{plugins/design/dev-server → apps/studio}/sync/untrusted.ts +0 -0
  303. package/{plugins/design/dev-server → apps/studio}/test/_helpers.ts +0 -0
  304. package/{plugins/design/dev-server → apps/studio}/test/active-state.test.ts +0 -0
  305. package/{plugins/design/dev-server → apps/studio}/test/activity.test.ts +0 -0
  306. package/{plugins/design/dev-server → apps/studio}/test/annotation-strokes-command.test.ts +0 -0
  307. package/{plugins/design/dev-server → apps/studio}/test/annotations-draw-modifiers.test.ts +2 -2
  308. package/{plugins/design/dev-server → apps/studio}/test/artboard-activity-overlay.test.tsx +0 -0
  309. package/{plugins/design/dev-server → apps/studio}/test/binary-smoke.test.ts +0 -0
  310. package/{plugins/design/dev-server → apps/studio}/test/bundle-smoke.test.ts +0 -0
  311. package/{plugins/design/dev-server → apps/studio}/test/canvas-build.test.ts +0 -0
  312. package/{plugins/design/dev-server → apps/studio}/test/canvas-header.test.ts +1 -1
  313. package/{plugins/design/dev-server → apps/studio}/test/canvas-lib-motion.test.ts +0 -0
  314. package/{plugins/design/dev-server → apps/studio}/test/canvas-pipeline.test.ts +0 -0
  315. package/{plugins/design/dev-server → apps/studio}/test/canvas-route.test.ts +0 -0
  316. package/{plugins/design/dev-server → apps/studio}/test/collab-awareness-bridge.test.ts +0 -0
  317. package/{plugins/design/dev-server → apps/studio}/test/collab-loopback.test.ts +0 -0
  318. package/{plugins/design/dev-server → apps/studio}/test/collab-protocol.test.ts +2 -2
  319. package/{plugins/design/dev-server → apps/studio}/test/collab-reseed-guard.test.ts +0 -0
  320. package/{plugins/design/dev-server → apps/studio}/test/comment-mount.test.ts +0 -0
  321. package/{plugins/design/dev-server → apps/studio}/test/context-resolve-tokens.test.ts +0 -0
  322. package/{plugins/design/dev-server → apps/studio}/test/csp-canvas-shell.test.ts +0 -0
  323. package/{plugins/design/dev-server → apps/studio}/test/equal-spacing-detector.test.ts +0 -0
  324. package/{plugins/design/dev-server → apps/studio}/test/exporters/endpoint.test.ts +0 -0
  325. package/{plugins/design/dev-server → apps/studio}/test/exporters/html.test.ts +0 -0
  326. package/{plugins/design/dev-server → apps/studio}/test/exporters/pdf.test.ts +0 -0
  327. package/{plugins/design/dev-server → apps/studio}/test/exporters/png.test.ts +0 -0
  328. package/{plugins/design/dev-server → apps/studio}/test/exporters/pptx-deck.test.ts +0 -0
  329. package/{plugins/design/dev-server → apps/studio}/test/exporters/pptx.test.ts +0 -0
  330. package/{plugins/design/dev-server → apps/studio}/test/exporters/svg.test.ts +0 -0
  331. package/{plugins/design/dev-server → apps/studio}/test/fixtures/phase-20-annotations.svg +0 -0
  332. package/{plugins/design/dev-server → apps/studio}/test/fixtures/phase-21-annotations.svg +0 -0
  333. package/{plugins/design/dev-server → apps/studio}/test/fs-watch.test.ts +0 -0
  334. package/{plugins/design/dev-server → apps/studio}/test/handoff.test.ts +1 -1
  335. package/{plugins/design/dev-server → apps/studio}/test/history-rollback.test.ts +0 -0
  336. package/{plugins/design/dev-server → apps/studio}/test/hmr-classify.test.ts +0 -0
  337. package/{plugins/design/dev-server → apps/studio}/test/input-router.test.ts +2 -2
  338. package/{plugins/design/dev-server → apps/studio}/test/locator.test.ts +1 -1
  339. package/{plugins/design/dev-server → apps/studio}/test/marquee-overlay.test.ts +0 -0
  340. package/{plugins/design/dev-server → apps/studio}/test/move-artboards-command.test.ts +0 -0
  341. package/{plugins/design/dev-server → apps/studio}/test/participants-chrome.test.ts +0 -0
  342. package/{plugins/design/dev-server → apps/studio}/test/paths.test.ts +0 -0
  343. package/{plugins/design/dev-server → apps/studio}/test/runtime-bundle-error-mapping.test.ts +0 -0
  344. package/{plugins/design/dev-server → apps/studio}/test/server-lifecycle.test.ts +0 -0
  345. package/{plugins/design/dev-server → apps/studio}/test/shared-doc-foundation.test.ts +0 -0
  346. package/{plugins/design/dev-server → apps/studio}/test/sync-codec.test.ts +1 -1
  347. package/{plugins/design/dev-server → apps/studio}/test/sync-meta-codec.test.ts +1 -1
  348. package/{plugins/design/dev-server → apps/studio}/test/system-endpoint.test.ts +0 -0
  349. package/{plugins/design/dev-server → apps/studio}/test/tool-palette.test.tsx +0 -0
  350. package/{plugins/design/dev-server → apps/studio}/test/undo-stack.test.ts +2 -2
  351. package/{plugins/design/dev-server → apps/studio}/test/use-annotation-selection.test.tsx +0 -0
  352. package/{plugins/design/dev-server → apps/studio}/test/use-artboard-drag.test.ts +3 -3
  353. package/{plugins/design/dev-server → apps/studio}/test/use-canvas-activity.test.tsx +2 -2
  354. package/{plugins/design/dev-server → apps/studio}/test/use-collab.test.ts +0 -0
  355. package/{plugins/design/dev-server → apps/studio}/test/use-cursor-modifiers.test.ts +0 -0
  356. package/{plugins/design/dev-server → apps/studio}/test/use-keyboard-discipline.test.ts +0 -0
  357. package/{plugins/design/dev-server → apps/studio}/test/use-selection-set.test.tsx +1 -1
  358. package/{plugins/design/dev-server → apps/studio}/test/use-undo-stack.test.tsx +2 -2
  359. /package/{plugins/design/dev-server → apps/studio}/test/ws-handshake.test.ts +0 -0
  360. /package/{plugins/design/dev-server → apps/studio}/tsconfig.json +0 -0
  361. /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
+ });
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
2
  import type { Rect } from '../geometry.ts';
3
- import { type LabelItem, diagram, modularScale, placeLabels, snapToGrid } from '../layout.ts';
3
+ import { diagram, type LabelItem, modularScale, placeLabels, snapToGrid } from '../layout.ts';
4
4
  import type { Point } from '../primitives.ts';
5
5
 
6
6
  describe('grid + modular scale', () => {
@@ -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
  import { describe, expect, test } from 'bun:test';
2
- import { path, circle, group, line, rect, text } from '../primitives.ts';
3
2
  import type { DrawPrimitive } from '../primitives.ts';
3
+ import { circle, group, line, path, rect, text } from '../primitives.ts';
4
4
  import { primitivesToNodes, toJsx, toSvg } from '../serialize.ts';
5
5
 
6
6
  const SAMPLE: DrawPrimitive[] = [
@@ -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
- type EqualSpacingResult,
19
- type SpacingAxis,
20
- detectEqualSpacing,
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;
@@ -20,9 +20,8 @@
20
20
  * Results land in .ai/decisions/DDR-024 + .ai/logs/phase-4-perf-<date>.md.
21
21
  */
22
22
 
23
- import { useEffect } from 'react';
24
-
25
23
  import { DCArtboard, DCSection, DesignCanvas } from '@maude/canvas-lib';
24
+ import { useEffect } from 'react';
26
25
 
27
26
  const ARTBOARD_COUNT = 100;
28
27
  const NODES_PER_ARTBOARD = 30;
@@ -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
@@ -14,8 +14,8 @@
14
14
  */
15
15
 
16
16
  import {
17
- type ReactNode,
18
17
  createContext,
18
+ type ReactNode,
19
19
  useCallback,
20
20
  useContext,
21
21
  useEffect,
@@ -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);
@@ -13,7 +13,7 @@ import * as html from './html.ts';
13
13
  import * as pdf from './pdf.ts';
14
14
  import * as png from './png.ts';
15
15
  import * as pptx from './pptx.ts';
16
- import { type ResolveScopeArgs, type Scope, type Target, resolveScope } from './scope.ts';
16
+ import { type ResolveScopeArgs, resolveScope, type Scope, type Target } from './scope.ts';
17
17
  import * as svg from './svg.ts';
18
18
  import * as zip from './zip.ts';
19
19
 
@@ -100,8 +100,8 @@ export async function runExport(args: {
100
100
  return adapter.run(targets, args.options, args.ctx);
101
101
  }
102
102
 
103
- export { resolveScope };
104
103
  export type { Scope, Target };
104
+ export { resolveScope };
105
105
 
106
106
  /**
107
107
  * Build the `_canvas-shell.html?canvas=…&tokens=…&components=…` URL the