@1agh/maude 0.28.1 → 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 (339) 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 +285 -5
  4. package/{plugins/design/dev-server → apps/studio}/annotations-layer.tsx +1206 -119
  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/screenshot.sh +62 -2
  16. package/{plugins/design/dev-server → apps/studio}/bin/server-up.sh +9 -3
  17. package/apps/studio/bin/to-lottie-verify.html +68 -0
  18. package/apps/studio/bin/to-lottie.sh +128 -0
  19. package/{plugins/design/dev-server → apps/studio}/build.ts +5 -3
  20. package/{plugins/design/dev-server → apps/studio}/canvas-arrowheads.ts +1 -1
  21. package/{plugins/design/dev-server → apps/studio}/canvas-comment-mount.tsx +1 -1
  22. package/apps/studio/canvas-create.ts +104 -0
  23. package/{plugins/design/dev-server → apps/studio}/canvas-cursors.ts +46 -12
  24. package/{plugins/design/dev-server → apps/studio}/canvas-icons.tsx +60 -1
  25. package/{plugins/design/dev-server → apps/studio}/canvas-lib-inline.ts +1 -1
  26. package/{plugins/design/dev-server → apps/studio}/canvas-lib-resolver.ts +1 -1
  27. package/{plugins/design/dev-server → apps/studio}/canvas-lib.tsx +31 -12
  28. package/{plugins/design/dev-server → apps/studio}/canvas-shell.tsx +56 -41
  29. package/{plugins/design/dev-server → apps/studio}/client/app.jsx +1713 -530
  30. package/apps/studio/client/canvas-url.js +81 -0
  31. package/{plugins/design/dev-server → apps/studio}/client/comments-overlay.css +41 -27
  32. package/{plugins/design/dev-server → apps/studio}/client/index.html +2 -1
  33. package/apps/studio/client/styles/1-tokens-maude.css +296 -0
  34. package/apps/studio/client/styles/3-shell-maude.css +459 -0
  35. package/{plugins/design/dev-server → apps/studio}/client/styles/3-shell.css +129 -0
  36. package/apps/studio/client/styles/4-components-maude.css +74 -0
  37. package/{plugins/design/dev-server → apps/studio}/client/styles/4-components.css +357 -34
  38. package/apps/studio/client/styles/5-maude-overrides.css +123 -0
  39. package/{plugins/design/dev-server → apps/studio}/client/styles/_index.css +9 -0
  40. package/{plugins/design/dev-server → apps/studio}/client/styles.css +1 -1
  41. package/apps/studio/client/tour/overlay.jsx +191 -0
  42. package/apps/studio/client/tour/usage-tour.js +35 -0
  43. package/apps/studio/client/whats-new-seen.js +52 -0
  44. package/apps/studio/client/whats-new.jsx +250 -0
  45. package/{plugins/design/dev-server → apps/studio}/commands/annotation-strokes-command.ts +1 -1
  46. package/{plugins/design/dev-server → apps/studio}/commands/equal-spacing-command.ts +1 -1
  47. package/{plugins/design/dev-server → apps/studio}/commands/move-artboards-command.ts +1 -1
  48. package/{plugins/design/dev-server → apps/studio}/comments-overlay.tsx +25 -6
  49. package/{plugins/design/dev-server → apps/studio}/context-menu.tsx +1 -1
  50. package/{plugins/design/dev-server → apps/studio}/contextual-toolbar.tsx +4 -7
  51. package/{plugins/design/dev-server → apps/studio}/cursors-overlay.tsx +1 -1
  52. package/apps/studio/dist/client.bundle.js +22825 -0
  53. package/apps/studio/dist/comment-mount.js +1960 -0
  54. package/apps/studio/dist/styles.css +5695 -0
  55. package/{plugins/design/dev-server → apps/studio}/dom-selection.ts +75 -4
  56. package/apps/studio/draw/animate.ts +333 -0
  57. package/{plugins/design/dev-server → apps/studio}/draw/brush.ts +1 -1
  58. package/{plugins/design/dev-server → apps/studio}/draw/composition.ts +1 -1
  59. package/{plugins/design/dev-server → apps/studio}/draw/geometry.ts +1 -1
  60. package/{plugins/design/dev-server → apps/studio}/draw/index.ts +4 -1
  61. package/{plugins/design/dev-server → apps/studio}/draw/layout.ts +1 -1
  62. package/apps/studio/draw/morph.ts +204 -0
  63. package/{plugins/design/dev-server → apps/studio}/draw/optimize.ts +1 -1
  64. package/{plugins/design/dev-server → apps/studio}/draw/palette.ts +1 -1
  65. package/{plugins/design/dev-server → apps/studio}/draw/primitives.ts +1 -1
  66. package/apps/studio/draw/serialize-animate.ts +324 -0
  67. package/{plugins/design/dev-server → apps/studio}/draw/serialize.ts +9 -3
  68. package/apps/studio/draw/test/animate.test.ts +172 -0
  69. package/apps/studio/draw/test/morph.test.ts +107 -0
  70. package/apps/studio/draw/test/serialize-animate.test.ts +142 -0
  71. package/{plugins/design/dev-server → apps/studio}/equal-spacing-detector.ts +1 -1
  72. package/{plugins/design/dev-server → apps/studio}/equal-spacing-handles.tsx +7 -29
  73. package/{plugins/design/dev-server → apps/studio}/export-dialog.tsx +13 -1
  74. package/{plugins/design/dev-server → apps/studio}/http.ts +102 -1
  75. package/{plugins/design/dev-server → apps/studio}/input-router.tsx +8 -1
  76. package/{plugins/design/dev-server → apps/studio}/inspect.ts +20 -3
  77. package/{plugins/design/dev-server → apps/studio}/marquee-overlay.tsx +5 -2
  78. package/{plugins/design/dev-server → apps/studio}/participants-chrome.tsx +1 -1
  79. package/{plugins/design/dev-server → apps/studio}/paths.ts +15 -11
  80. package/{plugins/design/dev-server → apps/studio}/server.ts +18 -0
  81. package/{plugins/design/dev-server → apps/studio}/sync/index.ts +1 -1
  82. package/{plugins/design/dev-server → apps/studio}/test/annotations-layer.test.ts +63 -0
  83. package/{plugins/design/dev-server → apps/studio}/test/annotations-roundtrip.test.ts +97 -0
  84. package/apps/studio/test/annotations-text-format.test.ts +396 -0
  85. package/apps/studio/test/asset-api.test.ts +149 -0
  86. package/apps/studio/test/canvas-create-api.test.ts +361 -0
  87. package/{plugins/design/dev-server → apps/studio}/test/canvas-cursors.test.ts +2 -0
  88. package/{plugins/design/dev-server → apps/studio}/test/canvas-lib-resolver.test.ts +1 -1
  89. package/apps/studio/test/canvas-media-drop.test.ts +158 -0
  90. package/{plugins/design/dev-server → apps/studio}/test/canvas-origin-gate.test.ts +17 -1
  91. package/apps/studio/test/canvas-url.test.ts +99 -0
  92. package/apps/studio/test/index-data-canvas-ds.test.ts +97 -0
  93. package/apps/studio/test/motion-sample.test.ts +35 -0
  94. package/{plugins/design/dev-server → apps/studio}/test/perf-harness.ts +1 -1
  95. package/{plugins/design/dev-server → apps/studio}/test/phase-3.6-smoke.test.ts +3 -1
  96. package/apps/studio/test/read-annotations.test.ts +409 -0
  97. package/{plugins/design/dev-server → apps/studio}/test/sanitize-annotation-svg.test.ts +108 -2
  98. package/{plugins/design/dev-server → apps/studio}/test/shared-doc-convergence.test.ts +1 -1
  99. package/apps/studio/test/tour-overlay.test.tsx +56 -0
  100. package/{plugins/design/dev-server → apps/studio}/test/use-annotation-resize.test.ts +43 -0
  101. package/{plugins/design/dev-server → apps/studio}/test/use-tool-mode.test.tsx +4 -1
  102. package/apps/studio/test/whats-new.test.ts +101 -0
  103. package/apps/studio/text-imports.d.ts +9 -0
  104. package/{plugins/design/dev-server → apps/studio}/tool-palette.tsx +4 -69
  105. package/{plugins/design/dev-server → apps/studio}/undo-hud.tsx +1 -1
  106. package/{plugins/design/dev-server → apps/studio}/undo-stack.ts +1 -1
  107. package/{plugins/design/dev-server → apps/studio}/use-agent-presence.tsx +1 -1
  108. package/{plugins/design/dev-server → apps/studio}/use-annotation-resize.tsx +40 -7
  109. package/{plugins/design/dev-server → apps/studio}/use-annotation-selection.tsx +1 -1
  110. package/{plugins/design/dev-server → apps/studio}/use-annotations-visibility.tsx +1 -1
  111. package/{plugins/design/dev-server → apps/studio}/use-artboard-drag.tsx +1 -1
  112. package/{plugins/design/dev-server → apps/studio}/use-canvas-activity.tsx +1 -1
  113. package/apps/studio/use-canvas-media-drop.tsx +343 -0
  114. package/{plugins/design/dev-server → apps/studio}/use-collab.tsx +1 -1
  115. package/{plugins/design/dev-server → apps/studio}/use-cursor-modifiers.tsx +1 -1
  116. package/{plugins/design/dev-server → apps/studio}/use-keyboard-discipline.tsx +5 -2
  117. package/{plugins/design/dev-server → apps/studio}/use-selection-set.tsx +9 -1
  118. package/{plugins/design/dev-server → apps/studio}/use-snap-guides.tsx +1 -1
  119. package/{plugins/design/dev-server → apps/studio}/use-tool-mode.tsx +3 -1
  120. package/{plugins/design/dev-server → apps/studio}/use-undo-stack.tsx +1 -1
  121. package/apps/studio/whats-new.json +139 -0
  122. package/apps/studio/whats-new.schema.json +70 -0
  123. package/apps/studio/whats-new.ts +87 -0
  124. package/cli/bin/maude.mjs +10 -0
  125. package/cli/commands/design.mjs +15 -7
  126. package/cli/commands/design.test.mjs +36 -0
  127. package/cli/commands/help.mjs +8 -2
  128. package/cli/commands/hub.mjs +3 -3
  129. package/cli/commands/hub.test.mjs +1 -1
  130. package/cli/commands/scenario-report.mjs +2 -9
  131. package/cli/lib/flow-design-integration.test.mjs +1 -1
  132. package/cli/lib/gitignore-block.mjs +1 -0
  133. package/cli/lib/plugin-cli-reachability.test.mjs +8 -7
  134. package/cli/lib/preflight.mjs +1 -1
  135. package/package.json +15 -15
  136. package/plugins/design/templates/brief-board.tsx.template +61 -0
  137. package/plugins/design/templates/canvas.tsx.template +1 -1
  138. package/plugins/design/templates/design-system-inspiration/core/config.json.tpl +1 -1
  139. package/plugins/design/templates/design-system-inspiration/core/preview/_motion-readme.md.tpl +1 -1
  140. package/plugins/flow/.claude-plugin/config.schema.json +16 -0
  141. package/plugins/design/dev-server/dist/client.bundle.js +0 -20692
  142. package/plugins/design/dev-server/dist/comment-mount.js +0 -1924
  143. package/plugins/design/dev-server/dist/styles.css +0 -2710
  144. /package/{plugins/design/dev-server → apps/studio}/activity.ts +0 -0
  145. /package/{plugins/design/dev-server → apps/studio}/bin/_enumerate-artboards-playwright.mjs +0 -0
  146. /package/{plugins/design/dev-server → apps/studio}/bin/_html-playwright.mjs +0 -0
  147. /package/{plugins/design/dev-server → apps/studio}/bin/_pdf-playwright.mjs +0 -0
  148. /package/{plugins/design/dev-server → apps/studio}/bin/_png-playwright.mjs +0 -0
  149. /package/{plugins/design/dev-server → apps/studio}/bin/_pptx-playwright.mjs +0 -0
  150. /package/{plugins/design/dev-server → apps/studio}/bin/_pw-launch.mjs +0 -0
  151. /package/{plugins/design/dev-server → apps/studio}/bin/_screenshot-playwright.mjs +0 -0
  152. /package/{plugins/design/dev-server → apps/studio}/bin/_svg-optimize.mjs +0 -0
  153. /package/{plugins/design/dev-server → apps/studio}/bin/_svg-playwright.mjs +0 -0
  154. /package/{plugins/design/dev-server → apps/studio}/bin/asset-sweep.sh +0 -0
  155. /package/{plugins/design/dev-server → apps/studio}/bin/bootstrap-check.sh +0 -0
  156. /package/{plugins/design/dev-server → apps/studio}/bin/check-runtime-bundles.sh +0 -0
  157. /package/{plugins/design/dev-server → apps/studio}/bin/draw-build.sh +0 -0
  158. /package/{plugins/design/dev-server → apps/studio}/bin/handoff.sh +0 -0
  159. /package/{plugins/design/dev-server → apps/studio}/bin/prep.sh +0 -0
  160. /package/{plugins/design/dev-server → apps/studio}/bin/scenario-report.mjs +0 -0
  161. /package/{plugins/design/dev-server → apps/studio}/bin/slug.sh +0 -0
  162. /package/{plugins/design/dev-server → apps/studio}/bin/smoke.sh +0 -0
  163. /package/{plugins/design/dev-server → apps/studio}/bin/svg-optimize.sh +0 -0
  164. /package/{plugins/design/dev-server → apps/studio}/bin/visual-sanity.sh +0 -0
  165. /package/{plugins/design/dev-server → apps/studio}/boot-self-heal.ts +0 -0
  166. /package/{plugins/design/dev-server → apps/studio}/canvas-build.ts +0 -0
  167. /package/{plugins/design/dev-server → apps/studio}/canvas-edit.ts +0 -0
  168. /package/{plugins/design/dev-server → apps/studio}/canvas-header.ts +0 -0
  169. /package/{plugins/design/dev-server → apps/studio}/canvas-meta.schema.json +0 -0
  170. /package/{plugins/design/dev-server → apps/studio}/canvas-pipeline.ts +0 -0
  171. /package/{plugins/design/dev-server → apps/studio}/client/hmr.mjs +0 -0
  172. /package/{plugins/design/dev-server → apps/studio}/client/iframe-lazy.mjs +0 -0
  173. /package/{plugins/design/dev-server → apps/studio}/client/styles/0-reset.css +0 -0
  174. /package/{plugins/design/dev-server → apps/studio}/client/styles/1-tokens.css +0 -0
  175. /package/{plugins/design/dev-server → apps/studio}/client/styles/2-layout.css +0 -0
  176. /package/{plugins/design/dev-server → apps/studio}/client/styles/5-utilities.css +0 -0
  177. /package/{plugins/design/dev-server → apps/studio}/collab/ai-activity.ts +0 -0
  178. /package/{plugins/design/dev-server → apps/studio}/collab/awareness-bridge.ts +0 -0
  179. /package/{plugins/design/dev-server → apps/studio}/collab/git-lifecycle.ts +0 -0
  180. /package/{plugins/design/dev-server → apps/studio}/collab/index.ts +0 -0
  181. /package/{plugins/design/dev-server → apps/studio}/collab/persistence.ts +0 -0
  182. /package/{plugins/design/dev-server → apps/studio}/collab/protocol.ts +0 -0
  183. /package/{plugins/design/dev-server → apps/studio}/collab/registry.ts +0 -0
  184. /package/{plugins/design/dev-server → apps/studio}/collab/room.ts +0 -0
  185. /package/{plugins/design/dev-server → apps/studio}/config.schema.json +0 -0
  186. /package/{plugins/design/dev-server → apps/studio}/context.ts +0 -0
  187. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/.min-sizes.json +0 -0
  188. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/lib0_decoding.js +0 -0
  189. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/lib0_encoding.js +0 -0
  190. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/motion.js +0 -0
  191. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/motion_react.js +0 -0
  192. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/pixi-js.js +0 -0
  193. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/react-dom.js +0 -0
  194. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/react-dom_client.js +0 -0
  195. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/react.js +0 -0
  196. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/react_jsx-dev-runtime.js +0 -0
  197. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/react_jsx-runtime.js +0 -0
  198. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/y-protocols_awareness.js +0 -0
  199. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/y-protocols_sync.js +0 -0
  200. /package/{plugins/design/dev-server → apps/studio}/dist/runtime/yjs.js +0 -0
  201. /package/{plugins/design/dev-server → apps/studio}/draw/test/brush.test.ts +0 -0
  202. /package/{plugins/design/dev-server → apps/studio}/draw/test/composition.test.ts +0 -0
  203. /package/{plugins/design/dev-server → apps/studio}/draw/test/geometry.test.ts +0 -0
  204. /package/{plugins/design/dev-server → apps/studio}/draw/test/gradient.test.ts +0 -0
  205. /package/{plugins/design/dev-server → apps/studio}/draw/test/layout.test.ts +0 -0
  206. /package/{plugins/design/dev-server → apps/studio}/draw/test/optimize.test.ts +0 -0
  207. /package/{plugins/design/dev-server → apps/studio}/draw/test/palette.test.ts +0 -0
  208. /package/{plugins/design/dev-server → apps/studio}/draw/test/primitives.test.ts +0 -0
  209. /package/{plugins/design/dev-server → apps/studio}/draw/test/serialize.test.ts +0 -0
  210. /package/{plugins/design/dev-server → apps/studio}/examples/README.md +0 -0
  211. /package/{plugins/design/dev-server → apps/studio}/examples/perf-100-artboards.tsx +0 -0
  212. /package/{plugins/design/dev-server → apps/studio}/exporters/_browser-bundles.ts +0 -0
  213. /package/{plugins/design/dev-server → apps/studio}/exporters/canva-handoff-prompt.ts +0 -0
  214. /package/{plugins/design/dev-server → apps/studio}/exporters/canva.ts +0 -0
  215. /package/{plugins/design/dev-server → apps/studio}/exporters/html.ts +0 -0
  216. /package/{plugins/design/dev-server → apps/studio}/exporters/index.ts +0 -0
  217. /package/{plugins/design/dev-server → apps/studio}/exporters/pdf.ts +0 -0
  218. /package/{plugins/design/dev-server → apps/studio}/exporters/png.ts +0 -0
  219. /package/{plugins/design/dev-server → apps/studio}/exporters/pptx.ts +0 -0
  220. /package/{plugins/design/dev-server → apps/studio}/exporters/scope.ts +0 -0
  221. /package/{plugins/design/dev-server → apps/studio}/exporters/svg.ts +0 -0
  222. /package/{plugins/design/dev-server → apps/studio}/exporters/zip.ts +0 -0
  223. /package/{plugins/design/dev-server → apps/studio}/fs-watch.ts +0 -0
  224. /package/{plugins/design/dev-server → apps/studio}/handoff.ts +0 -0
  225. /package/{plugins/design/dev-server → apps/studio}/history.ts +0 -0
  226. /package/{plugins/design/dev-server → apps/studio}/hmr-broadcast.ts +0 -0
  227. /package/{plugins/design/dev-server → apps/studio}/locator.ts +0 -0
  228. /package/{plugins/design/dev-server → apps/studio}/mem.ts +0 -0
  229. /package/{plugins/design/dev-server → apps/studio}/runtime-bundle.ts +0 -0
  230. /package/{plugins/design/dev-server → apps/studio}/server.mjs +0 -0
  231. /package/{plugins/design/dev-server → apps/studio}/sync/agent.ts +0 -0
  232. /package/{plugins/design/dev-server → apps/studio}/sync/atomic-write.ts +0 -0
  233. /package/{plugins/design/dev-server → apps/studio}/sync/codec.ts +0 -0
  234. /package/{plugins/design/dev-server → apps/studio}/sync/connection-state.ts +0 -0
  235. /package/{plugins/design/dev-server → apps/studio}/sync/echo-guard.ts +0 -0
  236. /package/{plugins/design/dev-server → apps/studio}/sync/fs-mirror.ts +0 -0
  237. /package/{plugins/design/dev-server → apps/studio}/sync/hubs-config.ts +0 -0
  238. /package/{plugins/design/dev-server → apps/studio}/sync/materialize.ts +0 -0
  239. /package/{plugins/design/dev-server → apps/studio}/sync/migrate-seed.ts +0 -0
  240. /package/{plugins/design/dev-server → apps/studio}/sync/origins.ts +0 -0
  241. /package/{plugins/design/dev-server → apps/studio}/sync/projection.ts +0 -0
  242. /package/{plugins/design/dev-server → apps/studio}/sync/status.ts +0 -0
  243. /package/{plugins/design/dev-server → apps/studio}/sync/untrusted.ts +0 -0
  244. /package/{plugins/design/dev-server → apps/studio}/test/_helpers.ts +0 -0
  245. /package/{plugins/design/dev-server → apps/studio}/test/active-state.test.ts +0 -0
  246. /package/{plugins/design/dev-server → apps/studio}/test/activity.test.ts +0 -0
  247. /package/{plugins/design/dev-server → apps/studio}/test/ai-activity.test.ts +0 -0
  248. /package/{plugins/design/dev-server → apps/studio}/test/annotation-strokes-command.test.ts +0 -0
  249. /package/{plugins/design/dev-server → apps/studio}/test/annotations-api.test.ts +0 -0
  250. /package/{plugins/design/dev-server → apps/studio}/test/annotations-draw-modifiers.test.ts +0 -0
  251. /package/{plugins/design/dev-server → apps/studio}/test/artboard-activity-overlay.test.tsx +0 -0
  252. /package/{plugins/design/dev-server → apps/studio}/test/binary-smoke.test.ts +0 -0
  253. /package/{plugins/design/dev-server → apps/studio}/test/boot-self-heal.test.ts +0 -0
  254. /package/{plugins/design/dev-server → apps/studio}/test/bundle-smoke.test.ts +0 -0
  255. /package/{plugins/design/dev-server → apps/studio}/test/canvas-build.test.ts +0 -0
  256. /package/{plugins/design/dev-server → apps/studio}/test/canvas-edit.test.ts +0 -0
  257. /package/{plugins/design/dev-server → apps/studio}/test/canvas-header.test.ts +0 -0
  258. /package/{plugins/design/dev-server → apps/studio}/test/canvas-hmr-runtime.test.tsx +0 -0
  259. /package/{plugins/design/dev-server → apps/studio}/test/canvas-lib-inline.test.ts +0 -0
  260. /package/{plugins/design/dev-server → apps/studio}/test/canvas-lib-motion.test.ts +0 -0
  261. /package/{plugins/design/dev-server → apps/studio}/test/canvas-meta-api.test.ts +0 -0
  262. /package/{plugins/design/dev-server → apps/studio}/test/canvas-pipeline.test.ts +0 -0
  263. /package/{plugins/design/dev-server → apps/studio}/test/canvas-route.test.ts +0 -0
  264. /package/{plugins/design/dev-server → apps/studio}/test/collab-annotations-bridge.test.ts +0 -0
  265. /package/{plugins/design/dev-server → apps/studio}/test/collab-awareness-bridge.test.ts +0 -0
  266. /package/{plugins/design/dev-server → apps/studio}/test/collab-bridge.test.ts +0 -0
  267. /package/{plugins/design/dev-server → apps/studio}/test/collab-loopback.test.ts +0 -0
  268. /package/{plugins/design/dev-server → apps/studio}/test/collab-protocol.test.ts +0 -0
  269. /package/{plugins/design/dev-server → apps/studio}/test/collab-reseed-guard.test.ts +0 -0
  270. /package/{plugins/design/dev-server → apps/studio}/test/collab-room.test.ts +0 -0
  271. /package/{plugins/design/dev-server → apps/studio}/test/collab-stress.test.ts +0 -0
  272. /package/{plugins/design/dev-server → apps/studio}/test/comment-mount.test.ts +0 -0
  273. /package/{plugins/design/dev-server → apps/studio}/test/comments-api.test.ts +0 -0
  274. /package/{plugins/design/dev-server → apps/studio}/test/compile-entry.test.ts +0 -0
  275. /package/{plugins/design/dev-server → apps/studio}/test/context-resolve-tokens.test.ts +0 -0
  276. /package/{plugins/design/dev-server → apps/studio}/test/csp-canvas-shell.test.ts +0 -0
  277. /package/{plugins/design/dev-server → apps/studio}/test/equal-spacing-detector.test.ts +0 -0
  278. /package/{plugins/design/dev-server → apps/studio}/test/exporters/canva.test.ts +0 -0
  279. /package/{plugins/design/dev-server → apps/studio}/test/exporters/endpoint.test.ts +0 -0
  280. /package/{plugins/design/dev-server → apps/studio}/test/exporters/history.test.ts +0 -0
  281. /package/{plugins/design/dev-server → apps/studio}/test/exporters/html.test.ts +0 -0
  282. /package/{plugins/design/dev-server → apps/studio}/test/exporters/pdf.test.ts +0 -0
  283. /package/{plugins/design/dev-server → apps/studio}/test/exporters/png.test.ts +0 -0
  284. /package/{plugins/design/dev-server → apps/studio}/test/exporters/pptx-deck.test.ts +0 -0
  285. /package/{plugins/design/dev-server → apps/studio}/test/exporters/pptx.test.ts +0 -0
  286. /package/{plugins/design/dev-server → apps/studio}/test/exporters/pw-launch.test.ts +0 -0
  287. /package/{plugins/design/dev-server → apps/studio}/test/exporters/scope.test.ts +0 -0
  288. /package/{plugins/design/dev-server → apps/studio}/test/exporters/svg.test.ts +0 -0
  289. /package/{plugins/design/dev-server → apps/studio}/test/exporters/zip.test.ts +0 -0
  290. /package/{plugins/design/dev-server → apps/studio}/test/fixtures/phase-20-annotations.svg +0 -0
  291. /package/{plugins/design/dev-server → apps/studio}/test/fixtures/phase-21-annotations.svg +0 -0
  292. /package/{plugins/design/dev-server → apps/studio}/test/fs-watch.test.ts +0 -0
  293. /package/{plugins/design/dev-server → apps/studio}/test/git-lifecycle.test.ts +0 -0
  294. /package/{plugins/design/dev-server → apps/studio}/test/handoff-static-frames.test.ts +0 -0
  295. /package/{plugins/design/dev-server → apps/studio}/test/handoff.test.ts +0 -0
  296. /package/{plugins/design/dev-server → apps/studio}/test/history-rollback.test.ts +0 -0
  297. /package/{plugins/design/dev-server → apps/studio}/test/hmr-broadcast.test.ts +0 -0
  298. /package/{plugins/design/dev-server → apps/studio}/test/hmr-classify.test.ts +0 -0
  299. /package/{plugins/design/dev-server → apps/studio}/test/input-router.test.ts +0 -0
  300. /package/{plugins/design/dev-server → apps/studio}/test/locator.test.ts +0 -0
  301. /package/{plugins/design/dev-server → apps/studio}/test/marquee-overlay.test.ts +0 -0
  302. /package/{plugins/design/dev-server → apps/studio}/test/move-artboards-command.test.ts +0 -0
  303. /package/{plugins/design/dev-server → apps/studio}/test/participants-chrome.test.ts +0 -0
  304. /package/{plugins/design/dev-server → apps/studio}/test/paths.test.ts +0 -0
  305. /package/{plugins/design/dev-server → apps/studio}/test/runtime-bundle-error-mapping.test.ts +0 -0
  306. /package/{plugins/design/dev-server → apps/studio}/test/runtime-bundle.test.ts +0 -0
  307. /package/{plugins/design/dev-server → apps/studio}/test/server-lifecycle.test.ts +0 -0
  308. /package/{plugins/design/dev-server → apps/studio}/test/shared-doc-foundation.test.ts +0 -0
  309. /package/{plugins/design/dev-server → apps/studio}/test/shared-doc-migrate.test.ts +0 -0
  310. /package/{plugins/design/dev-server → apps/studio}/test/shared-doc-projection.test.ts +0 -0
  311. /package/{plugins/design/dev-server → apps/studio}/test/snap-distance-pill.test.ts +0 -0
  312. /package/{plugins/design/dev-server → apps/studio}/test/sync-agent.test.ts +0 -0
  313. /package/{plugins/design/dev-server → apps/studio}/test/sync-atomic-write.test.ts +0 -0
  314. /package/{plugins/design/dev-server → apps/studio}/test/sync-codec.test.ts +0 -0
  315. /package/{plugins/design/dev-server → apps/studio}/test/sync-connection-state.test.ts +0 -0
  316. /package/{plugins/design/dev-server → apps/studio}/test/sync-echo-guard.test.ts +0 -0
  317. /package/{plugins/design/dev-server → apps/studio}/test/sync-fs-mirror.test.ts +0 -0
  318. /package/{plugins/design/dev-server → apps/studio}/test/sync-hardening.test.ts +0 -0
  319. /package/{plugins/design/dev-server → apps/studio}/test/sync-hubs-config.test.ts +0 -0
  320. /package/{plugins/design/dev-server → apps/studio}/test/sync-meta-codec.test.ts +0 -0
  321. /package/{plugins/design/dev-server → apps/studio}/test/sync-runtime.test.ts +0 -0
  322. /package/{plugins/design/dev-server → apps/studio}/test/sync-status.test.ts +0 -0
  323. /package/{plugins/design/dev-server → apps/studio}/test/sync-untrusted.test.ts +0 -0
  324. /package/{plugins/design/dev-server → apps/studio}/test/system-endpoint.test.ts +0 -0
  325. /package/{plugins/design/dev-server → apps/studio}/test/tool-palette.test.tsx +0 -0
  326. /package/{plugins/design/dev-server → apps/studio}/test/undo-stack.test.ts +0 -0
  327. /package/{plugins/design/dev-server → apps/studio}/test/use-agent-presence.test.tsx +0 -0
  328. /package/{plugins/design/dev-server → apps/studio}/test/use-annotation-selection.test.tsx +0 -0
  329. /package/{plugins/design/dev-server → apps/studio}/test/use-artboard-drag.test.ts +0 -0
  330. /package/{plugins/design/dev-server → apps/studio}/test/use-canvas-activity.test.tsx +0 -0
  331. /package/{plugins/design/dev-server → apps/studio}/test/use-collab.test.ts +0 -0
  332. /package/{plugins/design/dev-server → apps/studio}/test/use-cursor-modifiers.test.ts +0 -0
  333. /package/{plugins/design/dev-server → apps/studio}/test/use-keyboard-discipline.test.ts +0 -0
  334. /package/{plugins/design/dev-server → apps/studio}/test/use-selection-set.test.tsx +0 -0
  335. /package/{plugins/design/dev-server → apps/studio}/test/use-snap-guides.test.ts +0 -0
  336. /package/{plugins/design/dev-server → apps/studio}/test/use-undo-stack.test.tsx +0 -0
  337. /package/{plugins/design/dev-server → apps/studio}/test/ws-handshake.test.ts +0 -0
  338. /package/{plugins/design/dev-server → apps/studio}/tsconfig.json +0 -0
  339. /package/{plugins/design/dev-server → apps/studio}/ws.ts +0 -0
@@ -0,0 +1,129 @@
1
+ // _motion-sample-playwright.mjs — the live-motion proof sampler (draw-proof --motion).
2
+ //
3
+ // A still screenshot CANNOT prove animation — the studyfi-v3 session burned ~3
4
+ // rounds because a freeze-frame "confirmed" a dead CSS `d:path()` morph (which
5
+ // does not animate live in any shipping browser). This shim closes that gap: it
6
+ // loads the proof URL in a real Chromium, samples the animated element's
7
+ // geometry/paint signature at TWO distinct wall-clock times, and asserts it
8
+ // changed. Freeze-frame pass + over-time NO-CHANGE ⇒ HARD FAIL (DDR-094 M1).
9
+ //
10
+ // Signature = getBBox() (x/y/width/height — catches morph/scale/translate) +
11
+ // computed transform matrix + computed opacity (catches fade-only animation).
12
+ // Any component differing beyond epsilon ⇒ the property is animating.
13
+ //
14
+ // Target selection: an explicit --selector, else auto-detect the parent of the
15
+ // first <animate>/<animateTransform> in the document.
16
+ //
17
+ // Invocation (via draw-proof.sh; never a raw path — DDR-062):
18
+ // node _motion-sample-playwright.mjs --url <url> [--selector <css>]
19
+ // [--gap 600] [--timeout 12]
20
+ //
21
+ // Exit: 0 motion proven · 1 element/animation not found · 2 bad args
22
+ // 3 Chromium binary missing (launchChromium) · 4 NO over-time change (HARD FAIL)
23
+
24
+ /**
25
+ * Pure signature-delta test (exported for a unit test — no browser needed).
26
+ * Returns true when any component of two samples differs beyond `eps`.
27
+ */
28
+ export function signatureChanged(a, b, eps = 0.25) {
29
+ if (!a || !b) return false;
30
+ const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
31
+ for (const k of keys) {
32
+ if (k.startsWith('__')) continue;
33
+ const av = a[k];
34
+ const bv = b[k];
35
+ if (typeof av === 'number' && typeof bv === 'number') {
36
+ if (Math.abs(av - bv) > eps) return true;
37
+ } else if (av !== bv) {
38
+ return true;
39
+ }
40
+ }
41
+ return false;
42
+ }
43
+
44
+ // The in-page sampler: bbox + transform matrix + opacity of the target element.
45
+ // Exported so the selector/auto-detect contract is documented + reviewable.
46
+ export const SAMPLE_FN = (sel) => {
47
+ let el = null;
48
+ if (sel) {
49
+ el = document.querySelector(sel);
50
+ } else {
51
+ const anim = document.querySelector('animate, animateTransform');
52
+ el = anim ? anim.parentElement : null;
53
+ }
54
+ if (!el) return { __missing: true };
55
+ const box = typeof el.getBBox === 'function' ? el.getBBox() : { x: 0, y: 0, width: 0, height: 0 };
56
+ const cs = getComputedStyle(el);
57
+ return {
58
+ x: box.x,
59
+ y: box.y,
60
+ width: box.width,
61
+ height: box.height,
62
+ transform: cs.transform || 'none',
63
+ opacity: Number(cs.opacity) || 1,
64
+ };
65
+ };
66
+
67
+ // Entry-point detection that works under BOTH node and bun across versions
68
+ // (`import.meta.main` is Node-24-only). True only when run directly, so the
69
+ // unit test can `import { signatureChanged }` without launching Chromium.
70
+ const { pathToFileURL } = await import('node:url');
71
+ const isEntry = Boolean(process.argv[1]) && import.meta.url === pathToFileURL(process.argv[1]).href;
72
+
73
+ // Browser driver — runs ONLY as the entry point, never on import.
74
+ if (isEntry) {
75
+ const { launchChromium } = await import('./_pw-launch.mjs');
76
+
77
+ const args = Object.fromEntries(
78
+ process.argv.slice(2).reduce((acc, cur, i, all) => {
79
+ if (cur.startsWith('--')) acc.push([cur.slice(2), all[i + 1] ?? '1']);
80
+ return acc;
81
+ }, [])
82
+ );
83
+
84
+ const { url, selector, gap = '600', timeout = '12' } = args;
85
+ if (!url) {
86
+ console.error('usage: _motion-sample-playwright.mjs --url <url> [--selector <css>] [--gap ms]');
87
+ process.exit(2);
88
+ }
89
+ const gapMs = Number(gap);
90
+ const timeoutMs = Number(timeout) * 1000;
91
+
92
+ const browser = await launchChromium();
93
+ try {
94
+ const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
95
+ const page = await ctx.newPage();
96
+ await page.goto(url, { waitUntil: 'networkidle', timeout: timeoutMs });
97
+ await page.evaluate(() => document.fonts.ready);
98
+
99
+ const first = await page.evaluate(SAMPLE_FN, selector ?? null);
100
+ if (first.__missing) {
101
+ console.error(
102
+ selector
103
+ ? `motion-sample: no element matched "${selector}"`
104
+ : 'motion-sample: no <animate>/<animateTransform> found in the document'
105
+ );
106
+ process.exit(1);
107
+ }
108
+
109
+ await page.waitForTimeout(gapMs); // let the runtime advance real wall-clock
110
+ const second = await page.evaluate(SAMPLE_FN, selector ?? null);
111
+
112
+ if (signatureChanged(first, second)) {
113
+ console.error(`motion-sample: over-time delta confirmed (gap ${gapMs}ms) — motion proven`);
114
+ await browser.close();
115
+ process.exit(0);
116
+ }
117
+ console.error(
118
+ `motion-sample: NO change over ${gapMs}ms — dead mechanism (freeze-frame may look fine). ` +
119
+ 'Check for CSS d:path() or a non-animating property (DDR-094 M1/M2).'
120
+ );
121
+ console.error(` t0=${JSON.stringify(first)}`);
122
+ console.error(` t1=${JSON.stringify(second)}`);
123
+ await browser.close();
124
+ process.exit(4);
125
+ } catch (err) {
126
+ await browser.close();
127
+ throw err;
128
+ }
129
+ }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # canvas-edit.sh — AST-aware single-attribute edit on a canvas .tsx file.
3
- # Wraps `plugins/design/dev-server/canvas-edit.ts` so /design:edit Step 3a can
3
+ # Wraps `apps/studio/canvas-edit.ts` so /design:edit Step 3a can
4
4
  # shell out without booting a Bun module loader inline. Phase 3.6 Task 5.
5
5
  #
6
6
  # Usage: canvas-edit.sh <canvas-abs-path> <data-cd-id> <attr> <value>
@@ -13,13 +13,23 @@
13
13
  # draw-proof.sh --asset <mark.svg> --slug <name>
14
14
  # [--root <repo>] [--out-dir <dir>] [--name <display label>]
15
15
  # [--engine auto|agent-browser|playwright] [--timeout <secs>]
16
+ # [--motion] [--motion-selector <css>] [--motion-gap <ms>]
16
17
  #
17
18
  # Requires a running dev server (caller runs `maude design server-up` first);
18
19
  # reads the port from <designRoot>/_server.json.
19
20
  #
21
+ # --motion: after the still ladder, run the LIVE-MOTION proof — sample the
22
+ # animated element at two wall-clock times and assert it changed. A still
23
+ # screenshot can't prove animation (DDR-094 M1); a freeze-frame pass + an
24
+ # over-time NO-CHANGE is a HARD FAIL (dead mechanism, e.g. CSS d:path()).
25
+ # --motion-selector targets a specific element (default: auto-detect the
26
+ # parent of the first <animate>/<animateTransform>). --motion-gap sets the
27
+ # sample interval in ms (default 600).
28
+ #
20
29
  # Stdout (last line): the screenshot output directory (capturable in $(...)).
21
30
  # Stderr: progress / diagnostics.
22
- # Exit: 0 success / 1 missing input or server / 2 bad args / 3 capture failed.
31
+ # Exit: 0 success / 1 missing input or server / 2 bad args / 3 capture failed
32
+ # / 4 motion HARD FAIL (still ladder OK but no over-time change).
23
33
 
24
34
  ASSET=""
25
35
  SLUG=""
@@ -28,6 +38,9 @@ OUT_DIR=""
28
38
  NAME=""
29
39
  ENGINE="auto"
30
40
  TIMEOUT=10
41
+ MOTION=0
42
+ MOTION_SELECTOR=""
43
+ MOTION_GAP=600
31
44
 
32
45
  while [ $# -gt 0 ]; do
33
46
  case "$1" in
@@ -38,8 +51,11 @@ while [ $# -gt 0 ]; do
38
51
  --name) NAME="$2"; shift 2 ;;
39
52
  --engine) ENGINE="$2"; shift 2 ;;
40
53
  --timeout) TIMEOUT="$2"; shift 2 ;;
54
+ --motion) MOTION=1; shift ;;
55
+ --motion-selector) MOTION_SELECTOR="$2"; shift 2 ;;
56
+ --motion-gap) MOTION_GAP="$2"; shift 2 ;;
41
57
  --help|-h)
42
- sed -n '2,27p' "$0" | sed 's/^# \?//'
58
+ sed -n '2,37p' "$0" | sed 's/^# \?//'
43
59
  exit 0
44
60
  ;;
45
61
  *)
@@ -125,5 +141,24 @@ fi
125
141
 
126
142
  COUNT=$(find "$OUT_DIR" -maxdepth 1 -type f -name '*.png' 2>/dev/null | wc -l | tr -d ' ')
127
143
  echo "→ captured $COUNT proof image(s) in $OUT_DIR" >&2
144
+
145
+ # ---------- live-motion proof (--motion) ----------
146
+ # The still ladder above is the freeze-frame; it cannot prove the mark ANIMATES.
147
+ # Sample the animated element at two wall-clock times and require a delta.
148
+ if [ "$MOTION" -eq 1 ]; then
149
+ echo "→ live-motion proof (over-time delta) …" >&2
150
+ MOTION_ARGS=(--url "$URL" --gap "$MOTION_GAP" --timeout "$TIMEOUT")
151
+ [ -n "$MOTION_SELECTOR" ] && MOTION_ARGS+=(--selector "$MOTION_SELECTOR")
152
+ node "$SCRIPT_DIR/_motion-sample-playwright.mjs" "${MOTION_ARGS[@]}" >&2
153
+ MRC=$?
154
+ case "$MRC" in
155
+ 0) echo "→ motion proven (over-time delta confirmed)" >&2 ;;
156
+ 4) echo "draw-proof.sh: MOTION HARD FAIL — still ladder OK but the mark does NOT animate over time (dead mechanism). See DDR-094 M1/M2." >&2
157
+ exit 4 ;;
158
+ *) echo "draw-proof.sh: motion sampler error (rc=$MRC) — could not verify animation" >&2
159
+ exit 3 ;;
160
+ esac
161
+ fi
162
+
128
163
  # Last stdout line = the output dir, for $(maude design draw-proof ...) capture.
129
164
  printf '%s\n' "$OUT_DIR"
@@ -27,7 +27,7 @@
27
27
  # Exit: 0 if all hard deps pass; 1 otherwise.
28
28
 
29
29
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
30
- PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
30
+ PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}"
31
31
  PKG_ROOT="$(cd "$PLUGIN_ROOT/../.." && pwd)"
32
32
 
33
33
  if [ -f "$PKG_ROOT/cli/lib/preflight.mjs" ]; then
@@ -0,0 +1,438 @@
1
+ #!/usr/bin/env node
2
+ // read-annotations.mjs — headless, zero-dep SVG → JSON annotation reader.
3
+ //
4
+ // Phase 22. The browser-side parser `svgToStrokes` (annotations-layer.tsx) needs
5
+ // a DOMParser, which plain Node does not expose. This is its headless cousin: it
6
+ // reads a canvas's `<slug>.annotations.svg` and extracts every sticky / text /
7
+ // shape as structured JSON the `/design:new` ingest path folds into a generation
8
+ // brief. It is a SEPARATE implementation (not an import) of the same fixed
9
+ // vocabulary `strokesToSvg` serializes — `read-annotations.test.ts` rebuilds
10
+ // strokes through the CANONICAL serializer and asserts this reader recovers them,
11
+ // so any drift in the SVG shape (Phase 21 / 23 / 24 …) fails that test loud.
12
+ //
13
+ // Regex-over-known-vocabulary, mirroring the discipline of `sanitizeAnnotationSvg`
14
+ // (api.ts) and the defensive defaults of `svgToStrokes`. We never DOM-parse.
15
+ //
16
+ // Output: a JSON array of `{ tool, id, x, y, w, h, text, color }`. Strokes that
17
+ // carry words (sticky, text) populate `text`; position-only strokes (pen, rect,
18
+ // ellipse, polygon, arrow) emit `text: null` with their bounding box. With
19
+ // `--canvas-state <path>` each annotation also gets `artboard: <id|null>` by bbox
20
+ // overlap. Forward-compat: an unknown `data-tool` (Phase 23 image/link) passes
21
+ // through with any `href`/`url`/`title` it carries, so a later ingest picks up
22
+ // dropped reference media without a reader change.
23
+ //
24
+ // Reached via `maude design read-annotations "<rel-path>"` (DDR-062), never a raw
25
+ // bin path. A missing annotation file is NOT an error — a board with no notes is
26
+ // valid; the reader emits `[]` and exits 0.
27
+ //
28
+ // Usage:
29
+ // read-annotations.mjs <rel-path-relative-to-designRoot> [--root <repo>]
30
+ // [--canvas-state <path>] [--json]
31
+
32
+ import { existsSync, readFileSync } from 'node:fs';
33
+ import { isAbsolute, join, resolve } from 'node:path';
34
+
35
+ // ─────────────────────────────────────────────────────────────────────────────
36
+ // Argv
37
+
38
+ function parseArgv(argv) {
39
+ const out = { positional: [], root: null, canvasState: null, help: false };
40
+ for (let i = 0; i < argv.length; i += 1) {
41
+ const a = argv[i];
42
+ if (a === '--help' || a === '-h') {
43
+ out.help = true;
44
+ } else if (a === '--json') {
45
+ /* default — accepted for symmetry with other verbs */
46
+ } else if (a === '--root') {
47
+ i += 1;
48
+ out.root = argv[i];
49
+ } else if (a.startsWith('--root=')) {
50
+ out.root = a.slice('--root='.length);
51
+ } else if (a === '--canvas-state') {
52
+ i += 1;
53
+ out.canvasState = argv[i];
54
+ } else if (a.startsWith('--canvas-state=')) {
55
+ out.canvasState = a.slice('--canvas-state='.length);
56
+ } else {
57
+ out.positional.push(a);
58
+ }
59
+ }
60
+ return out;
61
+ }
62
+
63
+ const HELP = `read-annotations.mjs — headless SVG → JSON annotation reader (DDR-062 via \`maude design read-annotations\`)
64
+
65
+ Usage:
66
+ maude design read-annotations <rel-path> [--root <repo>] [--canvas-state <path>]
67
+
68
+ Args:
69
+ <rel-path> Canvas path relative to the design root (e.g. "ui/Foo.tsx").
70
+ --root <repo> Repo root. Default: $CLAUDE_PROJECT_DIR, then cwd.
71
+ --canvas-state <p> JSON of artboard rects ([{id,x,y,w,h}] or {artboards:[…]});
72
+ each annotation is tagged with the artboard it overlaps.
73
+ --json No-op (JSON is the only output form).
74
+
75
+ Emits a JSON array to stdout. A missing annotation file emits [] (exit 0).`;
76
+
77
+ // ─────────────────────────────────────────────────────────────────────────────
78
+ // Path + slug resolution — mirror the dev-server (api.ts fileSlug, context.ts
79
+ // designRoot). The annotation file is named by fileSlug(), so we recompute the
80
+ // SAME slug to find it.
81
+
82
+ function resolveDesignRoot(repoRoot) {
83
+ // config.json is ALWAYS at <repoRoot>/.design/config.json; its optional
84
+ // `designRoot` field (default ".design") is where canvases + annotation SVGs
85
+ // actually live. (context.ts:64,226 — designRoot defaults to ".design".)
86
+ let designRel = '.design';
87
+ try {
88
+ const cfg = JSON.parse(readFileSync(join(repoRoot, '.design', 'config.json'), 'utf8'));
89
+ if (typeof cfg.designRoot === 'string' && cfg.designRoot.trim()) {
90
+ designRel = cfg.designRoot.replace(/^\/+|\/+$/g, '');
91
+ }
92
+ } catch {
93
+ /* no config — default .design */
94
+ }
95
+ return { designRel, designRoot: join(repoRoot, designRel) };
96
+ }
97
+
98
+ // Byte-for-byte the producer's rule (api.ts:266 fileSlug). The reader's input is
99
+ // design-root-relative, but we still strip a leading designRel/ prefix so a
100
+ // repo-root-relative path ("./design/ui/Foo.tsx") resolves to the same slug.
101
+ function fileSlug(file, designRel) {
102
+ let p = String(file).replace(/^\/+|\/+$/g, '');
103
+ try {
104
+ p = decodeURIComponent(p);
105
+ } catch {
106
+ /* leave as-is */
107
+ }
108
+ const prefix = `${designRel.replace(/^\/+|\/+$/g, '')}/`;
109
+ if (p.startsWith(prefix)) p = p.slice(prefix.length);
110
+ return p
111
+ .replace(/\//g, '-')
112
+ .replace(/\s+/g, '_')
113
+ .replace(/\.(tsx|html)$/i, '')
114
+ .replace(/^\.+/, '')
115
+ .toLowerCase();
116
+ }
117
+
118
+ // ─────────────────────────────────────────────────────────────────────────────
119
+ // Tiny SVG helpers — attribute pluck, entity-decode, number coerce.
120
+
121
+ function attr(s, name) {
122
+ const re = new RegExp(
123
+ `(?:^|\\s)${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*=\\s*"([^"]*)"`
124
+ );
125
+ const m = s.match(re);
126
+ return m ? m[1] : null;
127
+ }
128
+
129
+ function hasAttr(s, name) {
130
+ return attr(s, name) !== null;
131
+ }
132
+
133
+ // Reverse esc() (annotations-layer.tsx): &amp; / &quot; / &lt; are the only
134
+ // entities the serializer writes, but we decode the common set defensively.
135
+ // Named entities first, &amp; LAST so "&amp;lt;" (a literal "&lt;") survives.
136
+ function decodeEntities(s) {
137
+ return String(s)
138
+ .replace(/&lt;/g, '<')
139
+ .replace(/&gt;/g, '>')
140
+ .replace(/&quot;/g, '"')
141
+ .replace(/&#39;/g, "'")
142
+ .replace(/&apos;/g, "'")
143
+ .replace(/&amp;/g, '&');
144
+ }
145
+
146
+ function num(v, d = 0) {
147
+ const n = Number.parseFloat(v);
148
+ return Number.isFinite(n) ? n : d;
149
+ }
150
+
151
+ function stripTags(s) {
152
+ return String(s).replace(/<[^>]*>/g, '');
153
+ }
154
+
155
+ // ─────────────────────────────────────────────────────────────────────────────
156
+ // Per-tool extraction.
157
+
158
+ function readText(attrs, inner) {
159
+ const anchorId = attr(attrs, 'data-anchor-id');
160
+ const standalone = !anchorId;
161
+ const o = {
162
+ tool: 'text',
163
+ id: attr(attrs, 'data-id') || '',
164
+ x: standalone ? num(attr(attrs, 'x')) : null,
165
+ y: standalone ? num(attr(attrs, 'y')) : null,
166
+ w: null,
167
+ h: null,
168
+ text: decodeEntities(inner).trim(),
169
+ color: attr(attrs, 'fill') || attr(attrs, 'stroke') || null,
170
+ };
171
+ if (anchorId) o.anchorId = anchorId;
172
+ return o;
173
+ }
174
+
175
+ function readSticky(attrs, inner) {
176
+ // Geometry off the inner <rect>; paper tint off the group fill; body off the
177
+ // inner <text>. (annotations-layer.tsx strokeToSvgEl 'sticky' branch.)
178
+ const rectAttrs = inner.match(/<rect\b([^>]*?)\/?>/)?.[1] ?? '';
179
+ const body = inner.match(/<text\b[^>]*>([\s\S]*?)<\/text>/)?.[1] ?? '';
180
+ return {
181
+ tool: 'sticky',
182
+ id: attr(attrs, 'data-id') || '',
183
+ x: num(attr(rectAttrs, 'x')),
184
+ y: num(attr(rectAttrs, 'y')),
185
+ w: num(attr(rectAttrs, 'width')),
186
+ h: num(attr(rectAttrs, 'height')),
187
+ text: decodeEntities(body).trim(),
188
+ color: attr(attrs, 'fill') || null,
189
+ };
190
+ }
191
+
192
+ function readArrow(attrs, inner) {
193
+ const id = attr(attrs, 'data-id') || '';
194
+ const color = attr(attrs, 'stroke') || null;
195
+ let ends = null;
196
+ const lineAttrs = inner.match(/<line\b([^>]*?)\/?>/)?.[1];
197
+ if (lineAttrs) {
198
+ ends = {
199
+ x1: num(attr(lineAttrs, 'x1')),
200
+ y1: num(attr(lineAttrs, 'y1')),
201
+ x2: num(attr(lineAttrs, 'x2')),
202
+ y2: num(attr(lineAttrs, 'y2')),
203
+ };
204
+ } else {
205
+ // Curved / elbow arrow persists a <path>; first + last coordinate pairs are
206
+ // the endpoints (arrowEndpoints in annotations-layer.tsx).
207
+ const d = inner.match(/<path\b([^>]*?)\/?>/)?.[1];
208
+ const nums = d ? (attr(d, 'd') || '').match(/-?\d+(?:\.\d+)?/g) : null;
209
+ if (nums && nums.length >= 4) {
210
+ ends = {
211
+ x1: num(nums[0]),
212
+ y1: num(nums[1]),
213
+ x2: num(nums[nums.length - 2]),
214
+ y2: num(nums[nums.length - 1]),
215
+ };
216
+ }
217
+ }
218
+ if (!ends) return { tool: 'arrow', id, x: null, y: null, w: null, h: null, text: null, color };
219
+ return {
220
+ tool: 'arrow',
221
+ id,
222
+ x: Math.min(ends.x1, ends.x2),
223
+ y: Math.min(ends.y1, ends.y2),
224
+ w: Math.abs(ends.x2 - ends.x1),
225
+ h: Math.abs(ends.y2 - ends.y1),
226
+ text: null,
227
+ color,
228
+ };
229
+ }
230
+
231
+ function bboxFromNums(nums) {
232
+ let xMin = Number.POSITIVE_INFINITY;
233
+ let yMin = Number.POSITIVE_INFINITY;
234
+ let xMax = Number.NEGATIVE_INFINITY;
235
+ let yMax = Number.NEGATIVE_INFINITY;
236
+ for (let i = 0; i + 1 < nums.length; i += 2) {
237
+ const px = num(nums[i]);
238
+ const py = num(nums[i + 1]);
239
+ if (px < xMin) xMin = px;
240
+ if (px > xMax) xMax = px;
241
+ if (py < yMin) yMin = py;
242
+ if (py > yMax) yMax = py;
243
+ }
244
+ if (!Number.isFinite(xMin)) return null;
245
+ return { x: xMin, y: yMin, w: xMax - xMin, h: yMax - yMin };
246
+ }
247
+
248
+ function readPen(attrs) {
249
+ const id = attr(attrs, 'data-id') || '';
250
+ const color = attr(attrs, 'stroke') || null;
251
+ const bb = bboxFromNums((attr(attrs, 'd') || '').match(/-?\d+(?:\.\d+)?/g) || []);
252
+ if (!bb) return { tool: 'pen', id, x: null, y: null, w: null, h: null, text: null, color };
253
+ return { tool: 'pen', id, ...bb, text: null, color };
254
+ }
255
+
256
+ function readRect(attrs) {
257
+ return {
258
+ tool: 'rect',
259
+ id: attr(attrs, 'data-id') || '',
260
+ x: num(attr(attrs, 'x')),
261
+ y: num(attr(attrs, 'y')),
262
+ w: num(attr(attrs, 'width')),
263
+ h: num(attr(attrs, 'height')),
264
+ text: null,
265
+ color: attr(attrs, 'stroke') || null,
266
+ };
267
+ }
268
+
269
+ function readEllipse(attrs) {
270
+ const cx = num(attr(attrs, 'cx'));
271
+ const cy = num(attr(attrs, 'cy'));
272
+ const rx = num(attr(attrs, 'rx'));
273
+ const ry = num(attr(attrs, 'ry'));
274
+ return {
275
+ tool: 'ellipse',
276
+ id: attr(attrs, 'data-id') || '',
277
+ x: cx - rx,
278
+ y: cy - ry,
279
+ w: rx * 2,
280
+ h: ry * 2,
281
+ text: null,
282
+ color: attr(attrs, 'stroke') || null,
283
+ };
284
+ }
285
+
286
+ function readPolygon(attrs) {
287
+ const id = attr(attrs, 'data-id') || '';
288
+ const color = attr(attrs, 'stroke') || null;
289
+ const nums = (attr(attrs, 'points') || '').match(/-?\d+(?:\.\d+)?/g) || [];
290
+ const bb = bboxFromNums(nums);
291
+ if (!bb) return { tool: 'polygon', id, x: null, y: null, w: null, h: null, text: null, color };
292
+ return { tool: 'polygon', id, ...bb, text: null, color };
293
+ }
294
+
295
+ // Forward-compat (Phase 23) — an unknown data-tool (image / link chip) passes
296
+ // through with whatever geometry + media attrs it carries.
297
+ function readUnknown(tool, attrs, inner) {
298
+ const text = inner
299
+ ? decodeEntities(stripTags(inner)).trim() || null
300
+ : attr(attrs, 'data-text')
301
+ ? decodeEntities(attr(attrs, 'data-text'))
302
+ : null;
303
+ const o = {
304
+ tool,
305
+ id: attr(attrs, 'data-id') || '',
306
+ x: hasAttr(attrs, 'x') ? num(attr(attrs, 'x')) : null,
307
+ y: hasAttr(attrs, 'y') ? num(attr(attrs, 'y')) : null,
308
+ w: hasAttr(attrs, 'width') ? num(attr(attrs, 'width')) : null,
309
+ h: hasAttr(attrs, 'height') ? num(attr(attrs, 'height')) : null,
310
+ text,
311
+ color: attr(attrs, 'fill') || attr(attrs, 'stroke') || null,
312
+ };
313
+ const href = attr(attrs, 'href') || attr(attrs, 'xlink:href');
314
+ const url = attr(attrs, 'data-url');
315
+ const title = attr(attrs, 'data-title');
316
+ if (href) o.href = href;
317
+ if (url) o.url = url;
318
+ if (title) o.title = decodeEntities(title);
319
+ return o;
320
+ }
321
+
322
+ // ─────────────────────────────────────────────────────────────────────────────
323
+ // Top-level element walk. The annotation vocabulary nests at most one level —
324
+ // sticky/arrow are <g> wrapping leaf primitives, text wraps only character data,
325
+ // and NO container nests inside the same container — so a non-greedy backref
326
+ // match recovers each top-level element in document order. (Task-2 drift guard
327
+ // keeps this invariant honest against the canonical serializer.)
328
+ function parseAnnotations(svg) {
329
+ const out = [];
330
+ if (!svg || !/<svg[\s>]/i.test(svg)) return out;
331
+ const re = /<(g|text)\b([^>]*)>([\s\S]*?)<\/\1>|<(path|rect|ellipse|polygon)\b([^>]*?)\/?>/g;
332
+ let m = re.exec(svg);
333
+ while (m !== null) {
334
+ if (m[1]) {
335
+ const attrs = m[2];
336
+ const inner = m[3];
337
+ const tool = attr(attrs, 'data-tool');
338
+ if (tool === 'sticky') out.push(readSticky(attrs, inner));
339
+ else if (tool === 'arrow') out.push(readArrow(attrs, inner));
340
+ else if (tool === 'text') out.push(readText(attrs, inner));
341
+ else if (tool) out.push(readUnknown(tool, attrs, inner));
342
+ } else {
343
+ const attrs = m[5];
344
+ const tool = attr(attrs, 'data-tool');
345
+ if (tool === 'pen') out.push(readPen(attrs));
346
+ else if (tool === 'rect') out.push(readRect(attrs));
347
+ else if (tool === 'ellipse') out.push(readEllipse(attrs));
348
+ else if (tool === 'polygon') out.push(readPolygon(attrs));
349
+ else if (tool) out.push(readUnknown(tool, attrs, ''));
350
+ }
351
+ m = re.exec(svg);
352
+ }
353
+ return out;
354
+ }
355
+
356
+ // ─────────────────────────────────────────────────────────────────────────────
357
+ // --canvas-state artboard overlap tagging.
358
+
359
+ function loadArtboards(p) {
360
+ try {
361
+ const raw = JSON.parse(readFileSync(p, 'utf8'));
362
+ const arr = Array.isArray(raw)
363
+ ? raw
364
+ : Array.isArray(raw.artboards)
365
+ ? raw.artboards
366
+ : Array.isArray(raw.layout?.artboards)
367
+ ? raw.layout.artboards
368
+ : [];
369
+ return arr.filter(
370
+ (r) => r && typeof r.id === 'string' && [r.x, r.y, r.w, r.h].every((n) => Number.isFinite(n))
371
+ );
372
+ } catch {
373
+ return [];
374
+ }
375
+ }
376
+
377
+ function tagArtboard(ann, artboards) {
378
+ if (ann.x == null || ann.y == null) return null;
379
+ const ax2 = ann.x + (ann.w || 0);
380
+ const ay2 = ann.y + (ann.h || 0);
381
+ for (const r of artboards) {
382
+ const rx2 = r.x + r.w;
383
+ const ry2 = r.y + r.h;
384
+ if (ann.x <= rx2 && ax2 >= r.x && ann.y <= ry2 && ay2 >= r.y) return r.id;
385
+ }
386
+ return null;
387
+ }
388
+
389
+ // ─────────────────────────────────────────────────────────────────────────────
390
+ // Main
391
+
392
+ function main() {
393
+ const args = parseArgv(process.argv.slice(2));
394
+ if (args.help) {
395
+ process.stdout.write(`${HELP}\n`);
396
+ return;
397
+ }
398
+ const relPath = args.positional[0];
399
+ if (!relPath) {
400
+ process.stderr.write(`read-annotations: missing <rel-path>.\n\n${HELP}\n`);
401
+ process.exit(2);
402
+ }
403
+
404
+ const repoRoot = args.root
405
+ ? resolve(args.root)
406
+ : process.env.CLAUDE_PROJECT_DIR
407
+ ? resolve(process.env.CLAUDE_PROJECT_DIR)
408
+ : process.cwd();
409
+
410
+ const { designRel, designRoot } = resolveDesignRoot(repoRoot);
411
+ const slug = fileSlug(relPath, designRel);
412
+ const svgPath = join(designRoot, `${slug}.annotations.svg`);
413
+
414
+ let svg = '';
415
+ if (existsSync(svgPath)) {
416
+ try {
417
+ svg = readFileSync(svgPath, 'utf8');
418
+ } catch {
419
+ svg = '';
420
+ }
421
+ }
422
+
423
+ let annotations = parseAnnotations(svg);
424
+
425
+ if (args.canvasState) {
426
+ const csPath = isAbsolute(args.canvasState)
427
+ ? args.canvasState
428
+ : resolve(process.cwd(), args.canvasState);
429
+ const artboards = loadArtboards(csPath);
430
+ if (artboards.length) {
431
+ annotations = annotations.map((a) => ({ ...a, artboard: tagArtboard(a, artboards) }));
432
+ }
433
+ }
434
+
435
+ process.stdout.write(`${JSON.stringify(annotations)}\n`);
436
+ }
437
+
438
+ main();
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bash
2
+ # read-annotations.sh — thin wrapper around read-annotations.mjs (the headless
3
+ # SVG → JSON annotation reader). Reached via `maude design read-annotations`
4
+ # (DDR-062), never a raw bin path. The reader is zero-dep Node, so we exec under
5
+ # `node` (always present — maude itself runs on Node), not Bun.
6
+ #
7
+ # Usage:
8
+ # read-annotations.sh <rel-path> [--root <repo>] [--canvas-state <path>]
9
+ # (see read-annotations.mjs --help for the full contract)
10
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
11
+ exec node "$SCRIPT_DIR/read-annotations.mjs" "$@"
@@ -61,7 +61,7 @@ if [ -z "$REPO" ]; then
61
61
  REPO="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
62
62
  fi
63
63
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
64
- PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
64
+ PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}"
65
65
  PREBUILT_DIR="$PLUGIN_ROOT/dev-server/dist/runtime"
66
66
  if [ ! -d "$PREBUILT_DIR" ]; then
67
67
  PREBUILT_DIR="$SCRIPT_DIR/../dist/runtime"