@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,343 @@
1
+ /**
2
+ * @file use-canvas-media-drop.tsx — Phase 23 canvas media intake
3
+ * @scope apps/studio/use-canvas-media-drop.tsx
4
+ * @purpose OS-level drag-and-drop + clipboard paste of images and URLs onto
5
+ * the canvas. Routes each gesture to a create callback owned by
6
+ * AnnotationsLayer (which holds the commit/undo sink + screenToWorld):
7
+ * • image file → `onImage(file, world)` → optimistic stroke + upload
8
+ * • http(s) URL → `onLink(url, title, world)` → client-only link chip
9
+ *
10
+ * The classification + URL helpers are PURE + exported so the unit
11
+ * tests cover the dispatch matrix without a real DataTransfer /
12
+ * ClipboardEvent. The hook itself only wires the DOM events and the
13
+ * drag-affordance class.
14
+ *
15
+ * Security: link URLs are gated to http(s) (no javascript:/data:);
16
+ * image bytes go through POST /_api/asset (magic-byte sniff + caps,
17
+ * DDR Task 9). Nothing here trusts the dropped content.
18
+ */
19
+
20
+ import { useEffect } from 'react';
21
+
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+ // Pure helpers (exported for unit tests)
24
+
25
+ /** A normalized snapshot of a DataTransfer / ClipboardData payload. */
26
+ export interface MediaPayload {
27
+ files: readonly File[];
28
+ /** text/uri-list (DnD) */
29
+ uriList: string;
30
+ /** text/html (carries the anchor text for a dragged/pasted link) */
31
+ html: string;
32
+ /** text/plain */
33
+ plain: string;
34
+ }
35
+
36
+ export type MediaIntent =
37
+ | { kind: 'image'; file: File }
38
+ | { kind: 'link'; url: string; title: string };
39
+
40
+ /** True only for an absolute http(s) URL — the one scheme a link chip accepts. */
41
+ export function isHttpUrl(value: string): boolean {
42
+ try {
43
+ const u = new URL(value.trim());
44
+ return u.protocol === 'http:' || u.protocol === 'https:';
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Normalize a single token into a safe http(s) URL, or null. You should NOT have
52
+ * to type a scheme to paste a link:
53
+ * - an explicit scheme is kept ONLY if it's http(s) — `javascript:` / `data:`
54
+ * / `file:` / `mailto:` all return null (never reach window.open);
55
+ * - a scheme-less but domain-shaped token (`example.com`, `sub.example.io/x`)
56
+ * gets `https://` prepended. The TLD must be alphabetic (≥2) so a bare
57
+ * decimal like `3.5` or `v1.2` is NOT mistaken for a domain.
58
+ */
59
+ export function normalizeUrl(raw: string): string | null {
60
+ const s = raw.trim();
61
+ if (!s) return null;
62
+ // Distinguish a real scheme (`http:`, `javascript:`) from a `host:port` colon.
63
+ // A scheme: the part before the FIRST colon is a valid scheme name (no `/`) AND
64
+ // the char after the colon is NOT a digit (a digit there is a port → scheme-less).
65
+ const colon = s.indexOf(':');
66
+ const hasScheme =
67
+ colon > 0 &&
68
+ /^[a-zA-Z][a-zA-Z0-9+.-]*$/.test(s.slice(0, colon)) &&
69
+ !/^\d/.test(s.slice(colon + 1));
70
+ if (hasScheme) return isHttpUrl(s) ? s : null;
71
+ // Scheme-less but domain-shaped (alphabetic TLD ≥2) → assume https.
72
+ if (/^([a-z0-9-]+\.)+[a-z]{2,}(:\d+)?(\/\S*)?$/i.test(s)) {
73
+ const u = `https://${s}`;
74
+ return isHttpUrl(u) ? u : null;
75
+ }
76
+ return null;
77
+ }
78
+
79
+ /**
80
+ * First link URL in a blob of text, normalized to http(s). Handles a uri-list
81
+ * (newline-separated, `#` comment lines), a bare-domain paste, and an explicit
82
+ * inline URL inside prose. Returns null when none.
83
+ */
84
+ export function firstHttpUrl(text: string): string | null {
85
+ if (!text) return null;
86
+ for (const raw of text.split(/[\r\n]+/)) {
87
+ const line = raw.trim();
88
+ if (!line || line.startsWith('#')) continue;
89
+ const u = normalizeUrl(line);
90
+ if (u) return u;
91
+ }
92
+ // Fall back to an explicit inline URL token inside prose (a scheme is required
93
+ // here — bare-domain detection only fires on a whole standalone line).
94
+ const m = text.match(/https?:\/\/[^\s"'<>]+/i);
95
+ return m ? normalizeUrl(m[0]) : null;
96
+ }
97
+
98
+ /** Hostname without a leading `www.` — the chip's domain label. */
99
+ export function linkDomain(url: string): string {
100
+ try {
101
+ return new URL(url).hostname.replace(/^www\./, '');
102
+ } catch {
103
+ return url;
104
+ }
105
+ }
106
+
107
+ /** A readable title fallback when no anchor text is available. */
108
+ export function prettifyUrl(url: string): string {
109
+ try {
110
+ const u = new URL(url);
111
+ const host = u.hostname.replace(/^www\./, '');
112
+ const path = u.pathname.replace(/\/+$/, '');
113
+ return path && path !== '/' ? `${host}${path}` : host;
114
+ } catch {
115
+ return url;
116
+ }
117
+ }
118
+
119
+ /** Anchor text from a `text/html` DnD/clipboard payload, else null. */
120
+ export function anchorTextFromHtml(html: string): string | null {
121
+ if (!html) return null;
122
+ const m = html.match(/<a\b[^>]*>([\s\S]*?)<\/a>/i);
123
+ if (!m?.[1]) return null;
124
+ const text = m[1]
125
+ .replace(/<[^>]*>/g, '')
126
+ .replace(/\s+/g, ' ')
127
+ .trim();
128
+ return text || null;
129
+ }
130
+
131
+ /**
132
+ * Decide what a drop / paste is: an image file wins over a URL (a dragged image
133
+ * carries both an image file AND a uri-list pointing at it). Returns null for a
134
+ * plain-text / non-media payload so the caller leaves the event untouched.
135
+ */
136
+ export function classifyMediaPayload(p: MediaPayload): MediaIntent | null {
137
+ const imageFile = p.files.find((f) => typeof f.type === 'string' && f.type.startsWith('image/'));
138
+ if (imageFile) return { kind: 'image', file: imageFile };
139
+ const url = firstHttpUrl(p.uriList) ?? firstHttpUrl(p.plain);
140
+ if (url) {
141
+ const title = anchorTextFromHtml(p.html) ?? prettifyUrl(url);
142
+ return { kind: 'link', url, title };
143
+ }
144
+ return null;
145
+ }
146
+
147
+ // ─────────────────────────────────────────────────────────────────────────────
148
+ // Upload
149
+
150
+ /** POST raw image bytes to the capped asset route. Returns the assets/ path. */
151
+ export async function uploadAsset(file: Blob): Promise<{ path: string } | { error: string }> {
152
+ try {
153
+ const res = await fetch('/_api/asset', {
154
+ method: 'POST',
155
+ headers: { 'Content-Type': file.type || 'application/octet-stream' },
156
+ body: file,
157
+ });
158
+ if (!res.ok) {
159
+ let msg = `upload failed (${res.status})`;
160
+ try {
161
+ const j = (await res.json()) as { error?: string };
162
+ if (j?.error) msg = j.error;
163
+ } catch {
164
+ /* non-JSON error body */
165
+ }
166
+ return { error: msg };
167
+ }
168
+ const j = (await res.json()) as { path?: string };
169
+ return typeof j?.path === 'string' ? { path: j.path } : { error: 'malformed upload response' };
170
+ } catch (e) {
171
+ return { error: e instanceof Error ? e.message : 'network error' };
172
+ }
173
+ }
174
+
175
+ // ─────────────────────────────────────────────────────────────────────────────
176
+ // Transient toast (no React state — self-contained DOM, mirrors ensure*Styles)
177
+
178
+ const TOAST_CSS = `
179
+ .dc-media-toast {
180
+ position: fixed;
181
+ left: 50%;
182
+ bottom: 108px;
183
+ transform: translateX(-50%);
184
+ z-index: 9;
185
+ max-width: 360px;
186
+ padding: 8px 14px;
187
+ border-radius: 8px;
188
+ background: #26262b;
189
+ color: #fff;
190
+ font-family: var(--maude-chrome-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
191
+ font-size: 12px;
192
+ box-shadow: 0 8px 28px rgba(0,0,0,0.34);
193
+ opacity: 0;
194
+ transition: opacity 140ms ease;
195
+ pointer-events: none;
196
+ }
197
+ .dc-media-toast[data-show="1"] { opacity: 1; }
198
+ /* Drop affordance — a focus-accent inset frame while a media drag is over the
199
+ canvas, so the surface reads as a drop target. */
200
+ .dc-media-dragover::after {
201
+ content: "";
202
+ position: fixed;
203
+ inset: 6px;
204
+ z-index: 8;
205
+ border: 2px dashed var(--maude-hud-accent, #d63b1f);
206
+ border-radius: 12px;
207
+ pointer-events: none;
208
+ }
209
+ @media (prefers-reduced-motion: reduce) {
210
+ .dc-media-toast { transition: none; }
211
+ }
212
+ `.trim();
213
+
214
+ function ensureMediaStyles(): void {
215
+ if (typeof document === 'undefined') return;
216
+ if (document.getElementById('dc-media-css')) return;
217
+ const s = document.createElement('style');
218
+ s.id = 'dc-media-css';
219
+ s.textContent = TOAST_CSS;
220
+ document.head.appendChild(s);
221
+ }
222
+
223
+ /** Show a brief auto-dismissing toast in the canvas (e.g. an upload failure). */
224
+ export function showCanvasToast(message: string): void {
225
+ if (typeof document === 'undefined') return;
226
+ ensureMediaStyles();
227
+ const el = document.createElement('div');
228
+ el.className = 'dc-media-toast';
229
+ el.setAttribute('role', 'status');
230
+ el.textContent = message;
231
+ document.body.appendChild(el);
232
+ requestAnimationFrame(() => el.setAttribute('data-show', '1'));
233
+ setTimeout(() => {
234
+ el.setAttribute('data-show', '0');
235
+ setTimeout(() => el.remove(), 200);
236
+ }, 2600);
237
+ }
238
+
239
+ // ─────────────────────────────────────────────────────────────────────────────
240
+ // Hook
241
+
242
+ export interface MediaDropCallbacks {
243
+ onImage: (file: File, world: [number, number]) => void;
244
+ onLink: (url: string, title: string, world: [number, number]) => void;
245
+ }
246
+
247
+ export function useCanvasMediaDrop(opts: {
248
+ enabled: boolean;
249
+ screenToWorld: (cx: number, cy: number) => [number, number];
250
+ callbacks: MediaDropCallbacks;
251
+ }): void {
252
+ const { enabled, screenToWorld, callbacks } = opts;
253
+ useEffect(() => {
254
+ if (!enabled || typeof document === 'undefined') return;
255
+ ensureMediaStyles();
256
+ const body = document.body;
257
+
258
+ const hasMedia = (dt: DataTransfer | null): boolean => {
259
+ if (!dt) return false;
260
+ const types = Array.from(dt.types ?? []);
261
+ return types.includes('Files') || types.includes('text/uri-list');
262
+ };
263
+
264
+ const payloadFromTransfer = (dt: DataTransfer): MediaPayload => ({
265
+ files: dt.files ? Array.from(dt.files) : [],
266
+ uriList: safeGet(dt, 'text/uri-list'),
267
+ html: safeGet(dt, 'text/html'),
268
+ plain: safeGet(dt, 'text/plain'),
269
+ });
270
+
271
+ const onDragOver = (e: DragEvent) => {
272
+ if (!hasMedia(e.dataTransfer)) return;
273
+ e.preventDefault();
274
+ if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
275
+ body.classList.add('dc-media-dragover');
276
+ };
277
+ const onDragLeave = (e: DragEvent) => {
278
+ // Only clear when the pointer actually leaves the window (related target
279
+ // null), not when crossing between child elements.
280
+ if (e.relatedTarget == null) body.classList.remove('dc-media-dragover');
281
+ };
282
+ const onDrop = (e: DragEvent) => {
283
+ body.classList.remove('dc-media-dragover');
284
+ if (!e.dataTransfer) return;
285
+ const intent = classifyMediaPayload(payloadFromTransfer(e.dataTransfer));
286
+ if (!intent) return; // not media — leave the event for other handlers
287
+ e.preventDefault();
288
+ const world = screenToWorld(e.clientX, e.clientY);
289
+ dispatchIntent(intent, world);
290
+ };
291
+ const onPaste = (e: ClipboardEvent) => {
292
+ const cd = e.clipboardData;
293
+ if (!cd) return;
294
+ const files: File[] = [];
295
+ if (cd.files) for (const f of Array.from(cd.files)) files.push(f);
296
+ // Some browsers expose pasted images only via items, not files.
297
+ if (cd.items) {
298
+ for (const it of Array.from(cd.items)) {
299
+ if (it.kind === 'file' && it.type.startsWith('image/')) {
300
+ const f = it.getAsFile();
301
+ if (f && !files.includes(f)) files.push(f);
302
+ }
303
+ }
304
+ }
305
+ const intent = classifyMediaPayload({
306
+ files,
307
+ uriList: safeGet(cd, 'text/uri-list'),
308
+ html: safeGet(cd, 'text/html'),
309
+ plain: safeGet(cd, 'text/plain'),
310
+ });
311
+ if (!intent) return; // plain-text paste — let the existing paste flow run
312
+ e.preventDefault();
313
+ // Paste has no pointer position — drop at the visible viewport centre.
314
+ const world = screenToWorld(window.innerWidth / 2, window.innerHeight / 2);
315
+ dispatchIntent(intent, world);
316
+ };
317
+
318
+ const dispatchIntent = (intent: MediaIntent, world: [number, number]) => {
319
+ if (intent.kind === 'image') callbacks.onImage(intent.file, world);
320
+ else if (isHttpUrl(intent.url)) callbacks.onLink(intent.url, intent.title, world);
321
+ };
322
+
323
+ document.addEventListener('dragover', onDragOver);
324
+ document.addEventListener('dragleave', onDragLeave);
325
+ document.addEventListener('drop', onDrop);
326
+ document.addEventListener('paste', onPaste);
327
+ return () => {
328
+ document.removeEventListener('dragover', onDragOver);
329
+ document.removeEventListener('dragleave', onDragLeave);
330
+ document.removeEventListener('drop', onDrop);
331
+ document.removeEventListener('paste', onPaste);
332
+ body.classList.remove('dc-media-dragover');
333
+ };
334
+ }, [enabled, screenToWorld, callbacks]);
335
+ }
336
+
337
+ function safeGet(dt: DataTransfer, type: string): string {
338
+ try {
339
+ return dt.getData(type) ?? '';
340
+ } catch {
341
+ return '';
342
+ }
343
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file use-collab.tsx — client-side Yjs collab provider for canvas iframes
3
- * @scope plugins/design/dev-server/use-collab.tsx
3
+ * @scope apps/studio/use-collab.tsx
4
4
  * @purpose Mounts a single Y.Doc + Awareness per canvas iframe. Opens a
5
5
  * WebSocket to `/_ws/collab/:slug`, speaks the y-websocket binary
6
6
  * protocol, exposes hooks for the cursor overlay + Task 3 comments
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file use-cursor-modifiers.tsx — T28 (Wave 3)
3
- * @scope plugins/design/dev-server/use-cursor-modifiers.tsx
3
+ * @scope apps/studio/use-cursor-modifiers.tsx
4
4
  * @purpose Modifier-aware cursor state machine. Tracks Alt / Shift / Meta
5
5
  * modifier state globally and reflects it as `data-mod-*`
6
6
  * attributes on `.dc-canvas`. CSS rules consume those attrs to
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file use-keyboard-discipline.tsx — T29 (Wave 3)
3
- * @scope plugins/design/dev-server/use-keyboard-discipline.tsx
3
+ * @scope apps/studio/use-keyboard-discipline.tsx
4
4
  * @purpose Keyboard nudge + Cmd+A select-all-in-artboard. Bail when focus
5
5
  * is inside an input / textarea / contenteditable so typing
6
6
  * into the dev-server's own inputs (file tree filter, etc.)
@@ -29,6 +29,7 @@
29
29
  import { useEffect } from 'react';
30
30
 
31
31
  import { useArtboardsContext, useDragStateContext } from './canvas-lib.tsx';
32
+ import { scopedCdSelector, selectorIndex } from './dom-selection.ts';
32
33
  import { isEditableTarget } from './input-router.tsx';
33
34
  import { type Selection, useSelectionSet } from './use-selection-set.tsx';
34
35
 
@@ -85,10 +86,12 @@ export function useKeyboardDiscipline(): void {
85
86
  for (const el of stamped) {
86
87
  const cdId = el.getAttribute('data-cd-id');
87
88
  if (!cdId) continue;
89
+ const kdSelector = scopedCdSelector(cdId, id);
88
90
  hits.push({
89
91
  id: cdId,
90
- selector: `[data-cd-id="${cdId}"]`,
92
+ selector: kdSelector,
91
93
  artboardId: id,
94
+ index: selectorIndex(document, kdSelector, el),
92
95
  tag: el.tagName.toLowerCase(),
93
96
  });
94
97
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file use-selection-set.tsx — Phase 4.1 multi-selection store
3
- * @scope plugins/design/dev-server/use-selection-set.tsx
3
+ * @scope apps/studio/use-selection-set.tsx
4
4
  * @purpose Multi-element selection state for canvas-shell. The canvas
5
5
  * input router calls `replace()` / `add()` / `clear()`;
6
6
  * the provider debounces and posts up to the dev-server shell
@@ -46,6 +46,14 @@ export interface Selection {
46
46
  selector: string;
47
47
  /** Artboard host (`data-dc-screen`) — for scoping multi-edits in future. */
48
48
  artboardId?: string | null;
49
+ /**
50
+ * Occurrence index of this element among `querySelectorAll(selector)`.
51
+ * data-cd-id is stamped per SOURCE element, so a component rendered N times
52
+ * (a list row, or a reusable used twice) yields N DOM nodes with the SAME
53
+ * id+artboard selector. The index disambiguates which instance — resolvers
54
+ * use `querySelectorAll(selector)[index]`. Absent/0 → first match.
55
+ */
56
+ index?: number;
49
57
  /** Snapshot fields filled by the router from `resolveHoverTarget`. */
50
58
  tag?: string;
51
59
  classes?: string;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file use-snap-guides.tsx — Phase 4.2 snap math + guide-line shapes
3
- * @scope plugins/design/dev-server/use-snap-guides.tsx
3
+ * @scope apps/studio/use-snap-guides.tsx
4
4
  * @purpose Pure function `computeSnap` that returns a snapped (x, y) for a
5
5
  * proposed artboard rect plus the guide lines that should render
6
6
  * as visual feedback. No React state; the drag controller calls
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file use-tool-mode.tsx — Phase 4.1 tool-mode store
3
- * @scope plugins/design/dev-server/use-tool-mode.tsx
3
+ * @scope apps/studio/use-tool-mode.tsx
4
4
  * @purpose Context + hook for the active canvas tool. Wired into
5
5
  * DesignCanvas. Phase 5 will
6
6
  * register additional tools (pen, circle, arrow, eraser) via
@@ -52,6 +52,8 @@ export const DEFAULT_TOOLS: readonly ToolDescriptor[] = Object.freeze([
52
52
  { id: 'hand', label: 'Hand', shortcut: 'H', cursor: TOOL_CURSORS.hand },
53
53
  { id: 'comment', label: 'Comment', shortcut: 'C', cursor: TOOL_CURSORS.comment },
54
54
  { id: 'pen', label: 'Pen', shortcut: 'B', cursor: TOOL_CURSORS.pen },
55
+ // Annotation polish (item 8) — highlighter sits next to the pen.
56
+ { id: 'highlighter', label: 'Highlighter', shortcut: 'I', cursor: TOOL_CURSORS.highlighter },
55
57
  // Phase 24 — one Shape tool replaces the separate Rect (R) + Ellipse (O)
56
58
  // buttons; the primitive is chosen from the palette popover.
57
59
  { id: 'shape', label: 'Shape', shortcut: 'R', cursor: TOOL_CURSORS.shape },
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file use-undo-stack.tsx — React Context + Provider for the undo stack
3
- * @scope plugins/design/dev-server/use-undo-stack.tsx
3
+ * @scope apps/studio/use-undo-stack.tsx
4
4
  * @purpose Wraps the pure `undoReducer` (undo-stack.ts) in React state,
5
5
  * owns the async runner that awaits side-effects before applying
6
6
  * the next state transition, exposes the `lastLabel` HUD signal,
@@ -0,0 +1,139 @@
1
+ {
2
+ "$schema": "./whats-new.schema.json",
3
+ "entries": [
4
+ {
5
+ "id": "studio-full-functionality",
6
+ "version": "0.29.0",
7
+ "date": "2026-06-08",
8
+ "kind": "feature",
9
+ "title": "The studio is fully wired now",
10
+ "summary": "Everything in the chrome is live: the top bar shows the real zoom + artboard count, the status bar always shows hub-sync, and your avatar (plus Claude when it's editing) appears in the menubar. The ⌘K palette grew real Canvas/Tools commands (New canvas, Export, Handoff, Draw), there's a proper Export & handoff dialog (PNG/PDF/SVG/HTML/shadcn/ZIP) right in the shell, and a new display-only Inspector panel (Inspect · Layers · CSS) reads any element you ⌘-click.",
11
+ "surface": "design-ui",
12
+ "tour": [
13
+ {
14
+ "target": ".st-mb-right",
15
+ "title": "Live status, finally",
16
+ "body": "Zoom, artboard count, presence and hub-sync now reflect the real state — no more static placeholders."
17
+ },
18
+ {
19
+ "target": ".st-rpanel",
20
+ "title": "Inspector",
21
+ "body": "⌘-click an element in the canvas, then press I — pos, size, ancestry and markup, read-only (live CSS knobs land in Phase 12)."
22
+ }
23
+ ]
24
+ },
25
+ {
26
+ "id": "draw-animation-lottie",
27
+ "version": "0.29.0",
28
+ "date": "2026-06-08",
29
+ "kind": "feature",
30
+ "title": "Animate draw marks → ship one Lottie",
31
+ "summary": "/design:draw now handles motion briefs (morph, pulse, blink) and proves the animation is actually live. /design:to-lottie productionizes it into one Lottie that plays 1:1 on web and React Native.",
32
+ "surface": "design-ui"
33
+ },
34
+ {
35
+ "id": "studio-maude-ds-redesign",
36
+ "version": "0.29.0",
37
+ "date": "2026-06-08",
38
+ "kind": "feature",
39
+ "title": "The studio, redesigned in maude",
40
+ "summary": "The canvas browser chrome is now built in the maude design system itself — a dark-first studio shell with a branded menubar + real dropdowns, a collapsible file-tree sidebar with a rail, a cohesive bottom context bar, a maude-styled comments panel, and a clean light handoff. New: a ⌘K command palette for jumping to the design-system view, comments, theme, reload, and help. The in-canvas chrome (tools, minimap, selection halos) was re-skinned to the same indigo accent so the whole surface reads as one product.",
41
+ "surface": "design-ui",
42
+ "tour": [
43
+ {
44
+ "target": ".st-menubar .st-brand",
45
+ "title": "Built in maude",
46
+ "body": "The studio chrome now dogfoods the maude DS — one cohesive material across the menubar, sidebar, canvas and status bar."
47
+ },
48
+ {
49
+ "target": ".st-menubar .st-whatsnew",
50
+ "title": "Press ⌘K",
51
+ "body": "A new command palette jumps you to the design-system view, comments, theme toggle, reload, and help — no mouse needed."
52
+ }
53
+ ]
54
+ },
55
+ {
56
+ "id": "annotation-tooling-polish",
57
+ "version": "0.29.0",
58
+ "date": "2026-06-08",
59
+ "kind": "improvement",
60
+ "title": "Richer annotations — highlighter, lists & text shortcuts",
61
+ "summary": "Annotations got a full polish pass: a new highlighter tool (translucent marker with multiply blend), dashed strokes on shapes, FigJam-style sticky corner, multi-line text with Enter, italic/underline/lists, and Cmd+B/I/U formatting shortcuts that work the same way in sticky notes, shapes, and the text tool.",
62
+ "surface": "design-ui",
63
+ "tour": [
64
+ {
65
+ "target": ".dc-tool-palette button[aria-label^='Highlighter']",
66
+ "title": "New: Highlighter",
67
+ "body": "A translucent marker pen (I) — overlapping strokes darken with multiply blend. Pick hue and nib width from the chrome below."
68
+ }
69
+ ]
70
+ },
71
+ {
72
+ "id": "canvas-media-images-links",
73
+ "version": "0.29.0",
74
+ "date": "2026-06-08",
75
+ "kind": "feature",
76
+ "title": "Drop images & paste links onto the canvas",
77
+ "summary": "Drag a screenshot from your desktop — or paste a clipboard image or a URL — straight onto the canvas. Images sit alongside your annotations; links render as a tidy preview chip you can open in one click.",
78
+ "surface": "design-ui"
79
+ },
80
+ {
81
+ "id": "in-app-whats-new-tour",
82
+ "version": "0.29.0",
83
+ "date": "2026-06-08",
84
+ "kind": "feature",
85
+ "title": "What's New, right in the canvas browser",
86
+ "summary": "The Maude UI now tells you what shipped — a dismissible notice plus a panel you can reopen from the menubar. Each closed-out feature adds an entry automatically.",
87
+ "surface": "design-ui",
88
+ "tour": [
89
+ {
90
+ "target": "[data-tour='whatsnew']",
91
+ "title": "Your What's New badge",
92
+ "body": "This ✦ badge lights up when a new maude version ships features. Click it any time to reopen this panel."
93
+ },
94
+ {
95
+ "target": "[data-tour='help']",
96
+ "title": "Tours live in Help too",
97
+ "body": "Open Help to replay the full \"how Maude works\" walkthrough whenever you want."
98
+ }
99
+ ]
100
+ },
101
+ {
102
+ "id": "canvas-create-delete-from-browser",
103
+ "version": "0.28.1",
104
+ "date": "2026-06-03",
105
+ "kind": "feature",
106
+ "title": "Create & delete canvases from the browser",
107
+ "summary": "Spin up a blank brief-board straight from the file tree (+ board) and remove canvases without leaving the UI — no /design:new round-trip needed.",
108
+ "learnMore": "https://github.com/1aGh/maude#readme",
109
+ "surface": "design-ui"
110
+ },
111
+ {
112
+ "id": "canvas-activity-overlay",
113
+ "version": "0.28.0",
114
+ "date": "2026-06-02",
115
+ "kind": "feature",
116
+ "title": "Live canvas activity overlay",
117
+ "summary": "See at a glance which artboard Claude (or a teammate) is editing — a soft rim + badge highlights the active canvas and fades when work settles.",
118
+ "surface": "design-ui"
119
+ },
120
+ {
121
+ "id": "draw-as-code-svg-agent",
122
+ "version": "0.28.0",
123
+ "date": "2026-05-30",
124
+ "kind": "feature",
125
+ "title": "Draw production SVGs as code",
126
+ "summary": "/design:draw generates logos, icons, and diagrams through a deterministic geometry engine — no hand-guessed path data — and proofs them across the 16/24/48/256 verify ladder.",
127
+ "surface": "design-ui"
128
+ },
129
+ {
130
+ "id": "self-hostable-hub-sync",
131
+ "version": "0.27.0",
132
+ "date": "2026-05-28",
133
+ "kind": "feature",
134
+ "title": "Self-hostable hub + live file sync",
135
+ "summary": "Run your own Maude hub and co-edit canvases with bidirectional file sync, offline-aware status, and conflict handling.",
136
+ "surface": "design-ui"
137
+ }
138
+ ]
139
+ }
@@ -0,0 +1,70 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/1aGh/maude/main/apps/studio/whats-new.schema.json",
4
+ "title": "Maude What's New feed",
5
+ "description": "User-facing product-update entries surfaced in the Maude UI (and mirrored to the docs site). Single source of truth — ships with the dev-server, appended on /flow:done, stamped at release. See .ai/plans/feature-in-app-whats-new-tour.md (DDR-A).",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["entries"],
9
+ "properties": {
10
+ "$schema": { "type": "string" },
11
+ "entries": {
12
+ "type": "array",
13
+ "items": { "$ref": "#/$defs/entry" }
14
+ }
15
+ },
16
+ "$defs": {
17
+ "entry": {
18
+ "type": "object",
19
+ "additionalProperties": false,
20
+ "required": ["id", "kind", "title", "summary"],
21
+ "properties": {
22
+ "id": {
23
+ "type": "string",
24
+ "description": "Stable slug (usually the plan slug). Used for de-dup and as the React key.",
25
+ "pattern": "^[a-z0-9][a-z0-9-]*$"
26
+ },
27
+ "version": {
28
+ "type": ["string", "null"],
29
+ "description": "Semver the feature shipped in, or null while pending (stamped at release).",
30
+ "pattern": "^\\d+\\.\\d+\\.\\d+"
31
+ },
32
+ "date": {
33
+ "type": ["string", "null"],
34
+ "description": "Release date YYYY-MM-DD, or null while pending."
35
+ },
36
+ "kind": { "enum": ["feature", "improvement", "usage", "fix"] },
37
+ "title": { "type": "string", "minLength": 1 },
38
+ "summary": { "type": "string", "minLength": 1 },
39
+ "learnMore": {
40
+ "type": "string",
41
+ "description": "Optional URL to docs / release notes. Constrained to http(s) — the feed renders this as an <a href>, so a javascript:/data: scheme is rejected at the data-contract layer (defense-in-depth alongside React's URL sanitizer).",
42
+ "pattern": "^https?://"
43
+ },
44
+ "surface": {
45
+ "type": "string",
46
+ "description": "Which UI surface this targets (e.g. design-ui). Future-proofing."
47
+ },
48
+ "tour": {
49
+ "type": "array",
50
+ "description": "Optional spotlight steps that highlight the new UI (consumed by the tour engine — Phase 3).",
51
+ "items": { "$ref": "#/$defs/tourStep" }
52
+ }
53
+ }
54
+ },
55
+ "tourStep": {
56
+ "type": "object",
57
+ "additionalProperties": false,
58
+ "required": ["target", "title", "body"],
59
+ "properties": {
60
+ "target": {
61
+ "type": "string",
62
+ "description": "CSS selector or [data-tour=\"<key>\"] anchor to spotlight."
63
+ },
64
+ "title": { "type": "string", "minLength": 1 },
65
+ "body": { "type": "string", "minLength": 1 },
66
+ "placement": { "enum": ["top", "bottom", "left", "right", "auto"] }
67
+ }
68
+ }
69
+ }
70
+ }