@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,56 @@
1
+ // tour-overlay — Phase 3. Render contract for the hand-rolled tour engine.
2
+ //
3
+ // renderToStaticMarkup doesn't run effects, so the rect stays null on the
4
+ // server render → the overlay falls back to a centered card + scrim. We assert
5
+ // the a11y dialog contract + step/nav wiring; keyboard/focus-trap behavior is a
6
+ // live-only concern (verified via agent-browser).
7
+
8
+ import { describe, expect, test } from 'bun:test';
9
+ import { renderToStaticMarkup } from 'react-dom/server';
10
+ import { TourOverlay } from '../client/tour/overlay.jsx';
11
+
12
+ const STEP = { target: '.x', title: 'Welcome', body: 'Body copy here' };
13
+
14
+ describe('TourOverlay', () => {
15
+ test('renders an accessible dialog with the first step', () => {
16
+ const html = renderToStaticMarkup(
17
+ <TourOverlay open steps={[STEP]} onClose={() => {}} onComplete={() => {}} />
18
+ );
19
+ expect(html).toContain('mdcc-tour');
20
+ expect(html).toContain('role="dialog"');
21
+ expect(html).toContain('aria-modal="true"');
22
+ expect(html).toContain('Welcome');
23
+ expect(html).toContain('Body copy here');
24
+ expect(html).toContain('1 / 1');
25
+ expect(html).toContain('mdcc-tour__scrim'); // no rect on the server → scrim, not spotlight
26
+ });
27
+
28
+ test('single step shows Done (not Next) and no Back', () => {
29
+ const html = renderToStaticMarkup(
30
+ <TourOverlay open steps={[STEP]} onClose={() => {}} onComplete={() => {}} />
31
+ );
32
+ expect(html).toContain('Done');
33
+ expect(html).not.toContain('mdcc-tour__back');
34
+ });
35
+
36
+ test('multi-step first page shows Next, the counter, and no Back yet', () => {
37
+ const steps = [STEP, { ...STEP, title: 'Two' }, { ...STEP, title: 'Three' }];
38
+ const html = renderToStaticMarkup(
39
+ <TourOverlay open steps={steps} onClose={() => {}} onComplete={() => {}} />
40
+ );
41
+ expect(html).toContain('1 / 3');
42
+ expect(html).toContain('Next');
43
+ expect(html).not.toContain('mdcc-tour__back'); // i === 0
44
+ });
45
+
46
+ test('closed or empty renders nothing', () => {
47
+ expect(
48
+ renderToStaticMarkup(
49
+ <TourOverlay open={false} steps={[STEP]} onClose={() => {}} onComplete={() => {}} />
50
+ )
51
+ ).toBe('');
52
+ expect(
53
+ renderToStaticMarkup(<TourOverlay open steps={[]} onClose={() => {}} onComplete={() => {}} />)
54
+ ).toBe('');
55
+ });
56
+ });
@@ -0,0 +1,402 @@
1
+ // use-annotation-resize — FigJam resize-modifier math (pure `resizeStroke`).
2
+ //
3
+ // Covers the no-modifier baseline (must stay algebraically identical to the
4
+ // pre-modifier corner math) plus Shift (aspect-lock / 45° snap), Alt
5
+ // (scale-from-center / mirror-from-midpoint), and Shift+Alt combined, across
6
+ // every resizable stroke kind. Pure function — no DOM render needed.
7
+
8
+ import { describe, expect, test } from 'bun:test';
9
+
10
+ import type {
11
+ ArrowStroke,
12
+ EllipseStroke,
13
+ ImageStroke,
14
+ LinkStroke,
15
+ PenStroke,
16
+ PolygonStroke,
17
+ RectStroke,
18
+ StickyStroke,
19
+ } from '../annotations-layer.tsx';
20
+ import { resizeStroke } from '../use-annotation-resize.tsx';
21
+
22
+ const rect: RectStroke = {
23
+ id: 'r1',
24
+ tool: 'rect',
25
+ color: '#000',
26
+ width: 2,
27
+ x: 0,
28
+ y: 0,
29
+ w: 100,
30
+ h: 50,
31
+ };
32
+
33
+ describe('resizeStroke / rect — no modifiers (baseline)', () => {
34
+ test('se corner anchors the nw corner (back-compat)', () => {
35
+ expect(resizeStroke(rect, 'se', 120, 80)).toEqual({ x: 0, y: 0, w: 120, h: 80 });
36
+ });
37
+
38
+ test('nw corner anchors the se corner', () => {
39
+ expect(resizeStroke(rect, 'nw', -20, -10)).toEqual({ x: -20, y: -10, w: 120, h: 60 });
40
+ });
41
+
42
+ test('dragging past the anchor flips the box (min/max parity)', () => {
43
+ // se dragged to the left of x=0 → box lives to the left of the anchor.
44
+ expect(resizeStroke(rect, 'se', -40, 80)).toEqual({ x: -40, y: 0, w: 40, h: 80 });
45
+ });
46
+ });
47
+
48
+ describe('resizeStroke / rect — Shift (aspect lock)', () => {
49
+ test('keeps the 2:1 start ratio, dominant axis drives scale', () => {
50
+ // raw 120×200 → scale = max(1.2, 4) = 4 → 400×200 (ratio preserved).
51
+ expect(resizeStroke(rect, 'se', 120, 200, { shift: true, alt: false })).toEqual({
52
+ x: 0,
53
+ y: 0,
54
+ w: 400,
55
+ h: 200,
56
+ });
57
+ });
58
+ });
59
+
60
+ describe('resizeStroke / rect — Alt (scale from center)', () => {
61
+ test('center stays fixed; both sides grow symmetrically', () => {
62
+ const out = resizeStroke(rect, 'se', 120, 80, { shift: false, alt: true });
63
+ expect(out).toEqual({ x: -20, y: -30, w: 140, h: 110 });
64
+ // center invariant: (-20 + 140/2, -30 + 110/2) === (50, 25) === start center.
65
+ expect((out as RectStroke).x + (out as RectStroke).w / 2).toBe(50);
66
+ expect((out as RectStroke).y + (out as RectStroke).h / 2).toBe(25);
67
+ });
68
+ });
69
+
70
+ describe('resizeStroke / rect — Shift+Alt (ratio + center)', () => {
71
+ test('center-anchored and ratio-locked at once', () => {
72
+ expect(resizeStroke(rect, 'se', 120, 200, { shift: true, alt: true })).toEqual({
73
+ x: -300,
74
+ y: -150,
75
+ w: 700,
76
+ h: 350,
77
+ });
78
+ });
79
+ });
80
+
81
+ describe('resizeStroke / polygon — routes through the shared bbox math', () => {
82
+ const poly: PolygonStroke = {
83
+ id: 'p1',
84
+ tool: 'polygon',
85
+ shape: 'diamond',
86
+ color: '#000',
87
+ width: 2,
88
+ x: 0,
89
+ y: 0,
90
+ w: 100,
91
+ h: 50,
92
+ };
93
+ test('Shift aspect-locks the polygon like a rect', () => {
94
+ expect(resizeStroke(poly, 'se', 120, 200, { shift: true, alt: false })).toEqual({
95
+ x: 0,
96
+ y: 0,
97
+ w: 400,
98
+ h: 200,
99
+ });
100
+ });
101
+ });
102
+
103
+ describe('resizeStroke / sticky — always 1:1 square', () => {
104
+ const sticky: StickyStroke = {
105
+ id: 's1',
106
+ tool: 'sticky',
107
+ color: '#fce8a6',
108
+ x: 0,
109
+ y: 0,
110
+ w: 100,
111
+ h: 100,
112
+ text: '',
113
+ fontSize: 16,
114
+ };
115
+ test('no modifiers — larger axis becomes the side', () => {
116
+ expect(resizeStroke(sticky, 'se', 150, 120)).toEqual({ x: 0, y: 0, w: 150, h: 150 });
117
+ });
118
+ test('Alt — square grows from the center', () => {
119
+ expect(resizeStroke(sticky, 'se', 150, 120, { shift: false, alt: true })).toEqual({
120
+ x: -50,
121
+ y: -50,
122
+ w: 200,
123
+ h: 200,
124
+ });
125
+ });
126
+ });
127
+
128
+ describe('resizeStroke / image — Phase 23 (aspect-lock by default, Shift frees)', () => {
129
+ const image: ImageStroke = {
130
+ id: 'im1',
131
+ tool: 'image',
132
+ x: 0,
133
+ y: 0,
134
+ w: 100,
135
+ h: 50, // 2:1 intrinsic aspect
136
+ href: 'assets/abcd1234.png',
137
+ };
138
+ test('no modifiers keeps the intrinsic aspect (the inverse of shapes)', () => {
139
+ // se → 120,80 would be 2.4:1 free, but the lock keeps 2:1 (dominant axis).
140
+ expect(resizeStroke(image, 'se', 120, 80)).toEqual({ x: 0, y: 0, w: 160, h: 80 });
141
+ });
142
+ test('Shift held inverts to a free (unconstrained) resize', () => {
143
+ expect(resizeStroke(image, 'se', 120, 80, { shift: true, alt: false })).toEqual({
144
+ x: 0,
145
+ y: 0,
146
+ w: 120,
147
+ h: 80,
148
+ });
149
+ });
150
+ });
151
+
152
+ describe('resizeStroke / link — Phase 23 (free-resizes like a rect)', () => {
153
+ const link: LinkStroke = {
154
+ id: 'lk1',
155
+ tool: 'link',
156
+ x: 0,
157
+ y: 0,
158
+ w: 200,
159
+ h: 80,
160
+ url: 'https://example.com',
161
+ title: 'Example',
162
+ domain: 'example.com',
163
+ };
164
+ test('no modifiers free-resizes (no forced aspect)', () => {
165
+ expect(resizeStroke(link, 'se', 300, 120)).toEqual({ x: 0, y: 0, w: 300, h: 120 });
166
+ });
167
+ });
168
+
169
+ describe('resizeStroke / ellipse — Alt keeps the center fixed', () => {
170
+ const ell: EllipseStroke = {
171
+ id: 'e1',
172
+ tool: 'ellipse',
173
+ color: '#000',
174
+ width: 2,
175
+ cx: 50,
176
+ cy: 50,
177
+ rx: 40,
178
+ ry: 20,
179
+ };
180
+ test('center invariant under symmetric scale', () => {
181
+ expect(resizeStroke(ell, 'se', 120, 90, { shift: false, alt: true })).toEqual({
182
+ cx: 50,
183
+ cy: 50,
184
+ rx: 70,
185
+ ry: 40,
186
+ });
187
+ });
188
+ });
189
+
190
+ describe('resizeStroke / arrow', () => {
191
+ const arrow: ArrowStroke = {
192
+ id: 'a1',
193
+ tool: 'arrow',
194
+ color: '#000',
195
+ width: 2,
196
+ x1: 0,
197
+ y1: 0,
198
+ x2: 100,
199
+ y2: 0,
200
+ };
201
+ test('no modifiers — only the dragged endpoint moves', () => {
202
+ expect(resizeStroke(arrow, 'ep2', 100, 40)).toEqual({ x2: 100, y2: 40 });
203
+ });
204
+ test('Alt — the midpoint is pinned; the far end mirrors', () => {
205
+ expect(resizeStroke(arrow, 'ep2', 100, 40, { shift: false, alt: true })).toEqual({
206
+ x2: 100,
207
+ y2: 40,
208
+ x1: 0,
209
+ y1: -40,
210
+ });
211
+ });
212
+ test('Shift — shaft angle snaps to the nearest 45°', () => {
213
+ const out = resizeStroke(arrow, 'ep2', 50, 60, { shift: true, alt: false }) as ArrowStroke;
214
+ // Pointer at atan2(60,50)≈49° snaps to 45° → dx === dy along the shaft.
215
+ expect(out.x2).toBeCloseTo(out.y2 ?? Number.NaN, 6);
216
+ expect(out.x2).toBeGreaterThan(0);
217
+ });
218
+ });
219
+
220
+ describe('resizeStroke / pen — point scaling', () => {
221
+ const pen: PenStroke = {
222
+ id: 'pen1',
223
+ tool: 'pen',
224
+ color: '#000',
225
+ width: 2,
226
+ points: [
227
+ [0, 0],
228
+ [100, 0],
229
+ [100, 100],
230
+ [0, 100],
231
+ ],
232
+ };
233
+ test('no modifiers — scales around the opposite corner', () => {
234
+ const out = resizeStroke(pen, 'se', 200, 200) as PenStroke;
235
+ expect(out.points[0]).toEqual([0, 0]);
236
+ expect(out.points[2]).toEqual([200, 200]);
237
+ });
238
+ test('Alt — scales around the bbox center', () => {
239
+ const out = resizeStroke(pen, 'se', 150, 150, { shift: false, alt: true }) as PenStroke;
240
+ expect(out.points[0]).toEqual([-50, -50]);
241
+ expect(out.points[2]).toEqual([150, 150]);
242
+ });
243
+ });
244
+
245
+ describe('resizeStroke / rotation — Wave G corner rotate zones', () => {
246
+ // rect center = (50, 25). Angles below are measured around it.
247
+ test('relative rotation: grab angle is the zero reference (no jump)', () => {
248
+ // Grab at angle 0° (due east of center), drag to 45° → rotation 45.
249
+ const rotRef = { angle0: 0, rot0: 0 };
250
+ const out = resizeStroke(
251
+ rect,
252
+ 'rot-se',
253
+ 50 + 70,
254
+ 25 + 70,
255
+ { shift: false, alt: false },
256
+ rotRef
257
+ );
258
+ expect(out).toEqual({ rotation: 45 });
259
+ });
260
+
261
+ test('starting rotation accumulates with the drag delta', () => {
262
+ const rotated: RectStroke = { ...rect, rotation: 30 };
263
+ const rotRef = { angle0: 0, rot0: 30 };
264
+ const out = resizeStroke(
265
+ rotated,
266
+ 'rot-ne',
267
+ 50 + 70,
268
+ 25 + 70,
269
+ { shift: false, alt: false },
270
+ rotRef
271
+ );
272
+ expect(out).toEqual({ rotation: 75 });
273
+ });
274
+
275
+ test('magnetic cardinals: within 2° of 0 lock on and serialize as undefined', () => {
276
+ const rotated: RectStroke = { ...rect, rotation: 44 };
277
+ // Drag back by 42.5° → raw −1.5° + rot0 44 = ... use angle0 50, current 7.5 → deg = 44 − 42.5 = 1.5 → locks to 0.
278
+ const cur = (Math.PI / 180) * 7.5;
279
+ const out = resizeStroke(
280
+ rotated,
281
+ 'rot-nw',
282
+ 50 + 70 * Math.cos(cur),
283
+ 25 + 70 * Math.sin(cur),
284
+ { shift: false, alt: false },
285
+ { angle0: 50, rot0: 44 }
286
+ );
287
+ expect(out).toEqual({ rotation: undefined });
288
+ });
289
+
290
+ test('Shift snaps to 15° steps', () => {
291
+ // delta = 40° → snaps to 45.
292
+ const cur = (Math.PI / 180) * 40;
293
+ const out = resizeStroke(
294
+ rect,
295
+ 'rot-sw',
296
+ 50 + 70 * Math.cos(cur),
297
+ 25 + 70 * Math.sin(cur),
298
+ { shift: true, alt: false },
299
+ { angle0: 0, rot0: 0 }
300
+ );
301
+ expect(out).toEqual({ rotation: 45 });
302
+ });
303
+
304
+ test('non-rotatable strokes refuse the rotate corners', () => {
305
+ const arrow: ArrowStroke = {
306
+ id: 'a1',
307
+ tool: 'arrow',
308
+ color: '#000',
309
+ width: 2,
310
+ x1: 0,
311
+ y1: 0,
312
+ x2: 100,
313
+ y2: 0,
314
+ };
315
+ expect(
316
+ resizeStroke(arrow, 'rot-se', 50, 50, { shift: false, alt: false }, { angle0: 0, rot0: 0 })
317
+ ).toBeNull();
318
+ });
319
+
320
+ test('polygon rotates (diamond/triangle are rotatable hosts)', () => {
321
+ const poly: PolygonStroke = {
322
+ id: 'p1',
323
+ tool: 'polygon',
324
+ shape: 'diamond',
325
+ color: '#000',
326
+ width: 2,
327
+ x: 0,
328
+ y: 0,
329
+ w: 100,
330
+ h: 50,
331
+ };
332
+ const out = resizeStroke(
333
+ poly,
334
+ 'rot-se',
335
+ 50 + 70,
336
+ 25 + 70,
337
+ { shift: false, alt: false },
338
+ { angle0: 0, rot0: 0 }
339
+ );
340
+ expect(out).toEqual({ rotation: 45 });
341
+ });
342
+ });
343
+
344
+ describe('resizeStroke / rotated strokes — Wave H anchor compensation', () => {
345
+ // rect 100×50 at origin, rotated 90° around its center (50, 25).
346
+ const rotated: RectStroke = { ...rect, rotation: 90 };
347
+ const rotP = (px: number, py: number, cx: number, cy: number, deg: number): [number, number] => {
348
+ const r = (deg * Math.PI) / 180;
349
+ const dx = px - cx;
350
+ const dy = py - cy;
351
+ return [cx + dx * Math.cos(r) - dy * Math.sin(r), cy + dx * Math.sin(r) + dy * Math.cos(r)];
352
+ };
353
+
354
+ test('se-corner drag keeps the nw corner fixed in WORLD space', () => {
355
+ // Cursor in the LOCAL frame (applyResize inverse-rotates before calling).
356
+ const out = resizeStroke(rotated, 'se', 140, 90) as RectStroke;
357
+ expect(out.w).toBeCloseTo(140, 6);
358
+ expect(out.h).toBeCloseTo(90, 6);
359
+ const before = rotP(0, 0, 50, 25, 90);
360
+ const after = rotP(out.x, out.y, out.x + out.w / 2, out.y + out.h / 2, 90);
361
+ expect(after[0]).toBeCloseTo(before[0], 6);
362
+ expect(after[1]).toBeCloseTo(before[1], 6);
363
+ });
364
+
365
+ test('e-edge drag keeps the west edge midpoint fixed in WORLD space', () => {
366
+ const out = resizeStroke(rotated, 'e', 130, 25) as RectStroke;
367
+ expect(out.w).toBeCloseTo(130, 6);
368
+ const before = rotP(0, 25, 50, 25, 90);
369
+ const after = rotP(out.x, out.y + out.h / 2, out.x + out.w / 2, out.y + out.h / 2, 90);
370
+ expect(after[0]).toBeCloseTo(before[0], 6);
371
+ expect(after[1]).toBeCloseTo(before[1], 6);
372
+ });
373
+
374
+ test('Alt resize keeps the center (no compensation path)', () => {
375
+ const out = resizeStroke(rotated, 'se', 130, 80, { shift: false, alt: true }) as RectStroke;
376
+ expect(out.x + out.w / 2).toBeCloseTo(50, 6);
377
+ expect(out.y + out.h / 2).toBeCloseTo(25, 6);
378
+ });
379
+
380
+ test('unrotated strokes are byte-stable (no shift applied)', () => {
381
+ expect(resizeStroke(rect, 'se', 120, 80)).toEqual({ x: 0, y: 0, w: 120, h: 80 });
382
+ });
383
+
384
+ test('rotated ellipse: se drag keeps the nw bbox corner fixed in WORLD space', () => {
385
+ const ell: EllipseStroke = {
386
+ id: 'e1',
387
+ tool: 'ellipse',
388
+ color: '#000',
389
+ width: 2,
390
+ cx: 50,
391
+ cy: 25,
392
+ rx: 50,
393
+ ry: 25,
394
+ rotation: 45,
395
+ };
396
+ const out = resizeStroke(ell, 'se', 140, 90) as EllipseStroke;
397
+ const before = rotP(0, 0, 50, 25, 45);
398
+ const after = rotP(out.cx - out.rx, out.cy - out.ry, out.cx, out.cy, 45);
399
+ expect(after[0]).toBeCloseTo(before[0], 6);
400
+ expect(after[1]).toBeCloseTo(before[1], 6);
401
+ });
402
+ });
@@ -15,7 +15,6 @@ import {
15
15
  DEFAULT_SNAP_TOLERANCE,
16
16
  DRAG_THRESHOLD_PX,
17
17
  type DragEvent,
18
- type DragState,
19
18
  dragReducer,
20
19
  selectionsToArtboardIds,
21
20
  } from '../use-artboard-drag.tsx';
@@ -1,6 +1,6 @@
1
1
  // use-tool-mode — Phase 4.1 Task 2. Provider transitions + cursor sync.
2
2
 
3
- import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
3
+ import { describe, expect, test } from 'bun:test';
4
4
 
5
5
  import { renderToStaticMarkup } from 'react-dom/server';
6
6
 
@@ -12,14 +12,16 @@ import {
12
12
  } from '../use-tool-mode.tsx';
13
13
 
14
14
  describe('use-tool-mode / static', () => {
15
- test('DEFAULT_TOOLS exposes V/H/C + draw set B/R/N/A/T/E (Phase 24 single Shape tool)', () => {
15
+ test('DEFAULT_TOOLS exposes V/H/C + draw set B/I/R/N/⇧S/A/T/E (single Shape tool + highlighter + section)', () => {
16
16
  expect(DEFAULT_TOOLS.map((t) => t.id)).toEqual([
17
17
  'move',
18
18
  'hand',
19
19
  'comment',
20
20
  'pen',
21
+ 'highlighter',
21
22
  'shape',
22
23
  'sticky',
24
+ 'section',
23
25
  'arrow',
24
26
  'text',
25
27
  'eraser',
@@ -29,8 +31,10 @@ describe('use-tool-mode / static', () => {
29
31
  'H',
30
32
  'C',
31
33
  'B',
34
+ 'I',
32
35
  'R',
33
36
  'N',
37
+ '⇧S',
34
38
  'A',
35
39
  'T',
36
40
  'E',
@@ -50,8 +54,10 @@ describe('use-tool-mode / static', () => {
50
54
  'hand',
51
55
  'comment',
52
56
  'pen',
57
+ 'highlighter',
53
58
  'shape',
54
59
  'sticky',
60
+ 'section',
55
61
  'arrow',
56
62
  'text',
57
63
  'eraser',
@@ -0,0 +1,101 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { mkdtempSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { bestSeenVersion, computeUnseen, parseVer, verGt } from '../client/whats-new-seen.js';
6
+ import { loadWhatsNew, resolveMaudeVersion } from '../whats-new.ts';
7
+
8
+ describe('whats-new feed loader', () => {
9
+ test('loads the committed feed with a version + non-empty entries', () => {
10
+ const feed = loadWhatsNew({ fresh: true });
11
+ expect(typeof feed.version).toBe('string');
12
+ expect(Array.isArray(feed.entries)).toBe(true);
13
+ expect(feed.entries.length).toBeGreaterThan(0);
14
+ });
15
+
16
+ test('every entry has the required shape', () => {
17
+ for (const e of loadWhatsNew({ fresh: true }).entries) {
18
+ expect(typeof e.id).toBe('string');
19
+ expect(e.id).toMatch(/^[a-z0-9][a-z0-9-]*$/);
20
+ expect(['feature', 'improvement', 'usage', 'fix']).toContain(e.kind);
21
+ expect(e.title.length).toBeGreaterThan(0);
22
+ expect(e.summary.length).toBeGreaterThan(0);
23
+ }
24
+ });
25
+
26
+ test('resolveMaudeVersion reads the design plugin manifest', () => {
27
+ expect(resolveMaudeVersion()).toMatch(/^\d+\.\d+\.\d+/);
28
+ });
29
+
30
+ test('a missing feed dir yields empty entries and never throws', () => {
31
+ const feed = loadWhatsNew({ root: join(import.meta.dir, '__does-not-exist__'), fresh: true });
32
+ expect(feed.entries).toEqual([]);
33
+ });
34
+
35
+ // Security regression (ethical-hacker creativity finding): the feed MUST
36
+ // resolve from the maude package root, NEVER a served project's tree. A
37
+ // hostile served project dropping its own whats-new.json must not inject
38
+ // entries into the privileged main-origin UI. Pin the boundary so a future
39
+ // "project-overridable feed" change can't silently erode it.
40
+ test('default resolution ignores a feed planted in an arbitrary (served-project) dir', () => {
41
+ const hostile = mkdtempSync(join(tmpdir(), 'mdcc-served-'));
42
+ writeFileSync(
43
+ join(hostile, 'whats-new.json'),
44
+ JSON.stringify({
45
+ entries: [{ id: 'evil', kind: 'feature', title: 'pwn', summary: 'x' }],
46
+ })
47
+ );
48
+ const feed = loadWhatsNew({ fresh: true }); // no root → package root only
49
+ expect(feed.entries.some((e) => e.id === 'evil')).toBe(false);
50
+ expect(feed.entries.length).toBeGreaterThan(0); // still reads the real package feed
51
+ });
52
+
53
+ // Defense-in-depth contract: a rendered learnMore is always an http(s) URL.
54
+ test('every committed entry learnMore is http(s) (no javascript:/data: scheme)', () => {
55
+ for (const e of loadWhatsNew({ fresh: true }).entries) {
56
+ if (e.learnMore != null) expect(e.learnMore).toMatch(/^https?:\/\//);
57
+ }
58
+ });
59
+ });
60
+
61
+ describe('seen logic (browser-safe, shared with the client)', () => {
62
+ test('parseVer extracts a semver triple', () => {
63
+ expect(parseVer('0.28.1')).toEqual([0, 28, 1]);
64
+ expect(parseVer('1.2.3-beta.4')).toEqual([1, 2, 3]);
65
+ expect(parseVer('dev')).toBeNull();
66
+ });
67
+
68
+ test('verGt compares semver, treating unparseable seen as oldest', () => {
69
+ expect(verGt('0.29.0', '0.28.1')).toBe(true);
70
+ expect(verGt('0.28.1', '0.29.0')).toBe(false);
71
+ expect(verGt('1.0.0', '0.99.99')).toBe(true);
72
+ expect(verGt('0.28.1', '0.28.1')).toBe(false);
73
+ expect(verGt('0.28.1', 'dev')).toBe(true);
74
+ expect(verGt('dev', '0.28.1')).toBe(false);
75
+ });
76
+
77
+ test('pending (null version) entries are always unseen', () => {
78
+ const u = computeUnseen(
79
+ [{ id: 'a', version: null, kind: 'feature', title: 't', summary: 's' }],
80
+ '9.9.9'
81
+ );
82
+ expect(u.map((e) => e.id)).toEqual(['a']);
83
+ });
84
+
85
+ test('computeUnseen returns only entries newer than the seen version', () => {
86
+ const entries = [
87
+ { id: 'old', version: '0.27.0', kind: 'feature', title: 't', summary: 's' },
88
+ { id: 'new', version: '0.29.0', kind: 'feature', title: 't', summary: 's' },
89
+ ];
90
+ expect(computeUnseen(entries, '0.28.0').map((e) => e.id)).toEqual(['new']);
91
+ expect(computeUnseen(entries, '0.29.0')).toEqual([]);
92
+ });
93
+
94
+ test('bestSeenVersion picks the highest of feed + entry versions', () => {
95
+ expect(bestSeenVersion([{ version: '0.29.0' }, { version: '0.27.0' }], '0.28.1')).toBe(
96
+ '0.29.0'
97
+ );
98
+ expect(bestSeenVersion([{ version: '0.20.0' }], '0.28.1')).toBe('0.28.1');
99
+ expect(bestSeenVersion([{ version: null }], '0.28.1')).toBe('0.28.1');
100
+ });
101
+ });
@@ -0,0 +1,9 @@
1
+ // Ambient declarations for Bun text imports — `import x from './f.ext' with { type: 'text' }`.
2
+ // The dev-server runs under Bun (which honors the import attribute) and ships as
3
+ // a `bun --compile` binary (which inlines the text at build time). tsc only needs
4
+ // to know the module resolves to a string default export.
5
+
6
+ declare module '*.tsx.template' {
7
+ const content: string;
8
+ export default content;
9
+ }