@1agh/maude 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/README.md +2 -2
  2. package/{plugins/design/dev-server → apps/studio}/ai-banner.tsx +1 -1
  3. package/{plugins/design/dev-server → apps/studio}/annotations-context-toolbar.tsx +286 -6
  4. package/{plugins/design/dev-server → apps/studio}/annotations-layer.tsx +1209 -122
  5. package/{plugins/design/dev-server → apps/studio}/api.ts +486 -32
  6. package/{plugins/design/dev-server → apps/studio}/artboard-activity-overlay.tsx +1 -1
  7. package/{plugins/design/dev-server → apps/studio}/artboard-marquee.tsx +1 -1
  8. package/apps/studio/bin/_motion-sample-playwright.mjs +129 -0
  9. package/{plugins/design/dev-server → apps/studio}/bin/canvas-edit.sh +1 -1
  10. package/{plugins/design/dev-server → apps/studio}/bin/draw-proof.sh +37 -2
  11. package/{plugins/design/dev-server → apps/studio}/bin/preflight.sh +1 -1
  12. package/apps/studio/bin/read-annotations.mjs +438 -0
  13. package/apps/studio/bin/read-annotations.sh +11 -0
  14. package/{plugins/design/dev-server → apps/studio}/bin/runtime-health.sh +1 -1
  15. package/{plugins/design/dev-server → apps/studio}/bin/scenario-report.mjs +1 -1
  16. package/{plugins/design/dev-server → apps/studio}/bin/screenshot.sh +62 -2
  17. package/apps/studio/bin/server-up.sh +234 -0
  18. package/apps/studio/bin/to-lottie-verify.html +68 -0
  19. package/apps/studio/bin/to-lottie.sh +128 -0
  20. package/{plugins/design/dev-server → apps/studio}/bin/visual-sanity.sh +19 -3
  21. package/{plugins/design/dev-server → apps/studio}/build.ts +5 -3
  22. package/{plugins/design/dev-server → apps/studio}/canvas-arrowheads.ts +1 -1
  23. package/{plugins/design/dev-server → apps/studio}/canvas-comment-mount.tsx +2 -2
  24. package/apps/studio/canvas-create.ts +104 -0
  25. package/{plugins/design/dev-server → apps/studio}/canvas-cursors.ts +47 -13
  26. package/{plugins/design/dev-server → apps/studio}/canvas-icons.tsx +60 -1
  27. package/{plugins/design/dev-server → apps/studio}/canvas-lib-inline.ts +1 -1
  28. package/{plugins/design/dev-server → apps/studio}/canvas-lib-resolver.ts +1 -1
  29. package/{plugins/design/dev-server → apps/studio}/canvas-lib.tsx +41 -33
  30. package/{plugins/design/dev-server → apps/studio}/canvas-shell.tsx +57 -42
  31. package/{plugins/design/dev-server → apps/studio}/client/app.jsx +1713 -530
  32. package/apps/studio/client/canvas-url.js +81 -0
  33. package/{plugins/design/dev-server → apps/studio}/client/comments-overlay.css +41 -27
  34. package/{plugins/design/dev-server → apps/studio}/client/index.html +2 -1
  35. package/apps/studio/client/styles/1-tokens-maude.css +296 -0
  36. package/apps/studio/client/styles/3-shell-maude.css +459 -0
  37. package/{plugins/design/dev-server → apps/studio}/client/styles/3-shell.css +129 -0
  38. package/apps/studio/client/styles/4-components-maude.css +74 -0
  39. package/{plugins/design/dev-server → apps/studio}/client/styles/4-components.css +357 -34
  40. package/apps/studio/client/styles/5-maude-overrides.css +123 -0
  41. package/{plugins/design/dev-server → apps/studio}/client/styles/_index.css +9 -0
  42. package/{plugins/design/dev-server → apps/studio}/client/styles.css +1 -1
  43. package/apps/studio/client/tour/overlay.jsx +191 -0
  44. package/apps/studio/client/tour/usage-tour.js +35 -0
  45. package/apps/studio/client/whats-new-seen.js +52 -0
  46. package/apps/studio/client/whats-new.jsx +250 -0
  47. package/{plugins/design/dev-server → apps/studio}/collab/index.ts +4 -4
  48. package/{plugins/design/dev-server → apps/studio}/collab/persistence.ts +1 -1
  49. package/{plugins/design/dev-server → apps/studio}/commands/annotation-strokes-command.ts +1 -1
  50. package/{plugins/design/dev-server → apps/studio}/commands/equal-spacing-command.ts +1 -1
  51. package/{plugins/design/dev-server → apps/studio}/commands/move-artboards-command.ts +1 -1
  52. package/{plugins/design/dev-server → apps/studio}/comments-overlay.tsx +25 -6
  53. package/{plugins/design/dev-server → apps/studio}/context-menu.tsx +2 -2
  54. package/{plugins/design/dev-server → apps/studio}/contextual-toolbar.tsx +4 -7
  55. package/{plugins/design/dev-server → apps/studio}/cursors-overlay.tsx +1 -1
  56. package/apps/studio/dist/client.bundle.js +22825 -0
  57. package/apps/studio/dist/comment-mount.js +1960 -0
  58. package/apps/studio/dist/runtime/lib0_decoding.js +1 -0
  59. package/apps/studio/dist/runtime/lib0_encoding.js +1 -0
  60. package/apps/studio/dist/runtime/motion.js +4 -0
  61. package/apps/studio/dist/runtime/motion_react.js +12 -0
  62. package/apps/studio/dist/runtime/pixi-js.js +3397 -0
  63. package/apps/studio/dist/runtime/react-dom.js +1 -0
  64. package/apps/studio/dist/runtime/react-dom_client.js +9 -0
  65. package/apps/studio/dist/runtime/react.js +1 -0
  66. package/apps/studio/dist/runtime/react_jsx-dev-runtime.js +1 -0
  67. package/apps/studio/dist/runtime/react_jsx-runtime.js +1 -0
  68. package/apps/studio/dist/runtime/y-protocols_awareness.js +1 -0
  69. package/apps/studio/dist/runtime/y-protocols_sync.js +1 -0
  70. package/apps/studio/dist/runtime/yjs.js +5 -0
  71. package/apps/studio/dist/styles.css +5695 -0
  72. package/{plugins/design/dev-server → apps/studio}/dom-selection.ts +75 -4
  73. package/apps/studio/draw/animate.ts +333 -0
  74. package/{plugins/design/dev-server → apps/studio}/draw/brush.ts +4 -4
  75. package/{plugins/design/dev-server → apps/studio}/draw/composition.ts +1 -1
  76. package/{plugins/design/dev-server → apps/studio}/draw/geometry.ts +1 -1
  77. package/{plugins/design/dev-server → apps/studio}/draw/index.ts +9 -6
  78. package/{plugins/design/dev-server → apps/studio}/draw/layout.ts +2 -2
  79. package/apps/studio/draw/morph.ts +204 -0
  80. package/{plugins/design/dev-server → apps/studio}/draw/optimize.ts +1 -1
  81. package/{plugins/design/dev-server → apps/studio}/draw/palette.ts +1 -1
  82. package/{plugins/design/dev-server → apps/studio}/draw/primitives.ts +1 -1
  83. package/apps/studio/draw/serialize-animate.ts +324 -0
  84. package/{plugins/design/dev-server → apps/studio}/draw/serialize.ts +9 -3
  85. package/apps/studio/draw/test/animate.test.ts +172 -0
  86. package/{plugins/design/dev-server → apps/studio}/draw/test/layout.test.ts +1 -1
  87. package/apps/studio/draw/test/morph.test.ts +107 -0
  88. package/apps/studio/draw/test/serialize-animate.test.ts +142 -0
  89. package/{plugins/design/dev-server → apps/studio}/draw/test/serialize.test.ts +1 -1
  90. package/{plugins/design/dev-server → apps/studio}/equal-spacing-detector.ts +1 -1
  91. package/{plugins/design/dev-server → apps/studio}/equal-spacing-handles.tsx +7 -29
  92. package/{plugins/design/dev-server → apps/studio}/examples/perf-100-artboards.tsx +1 -2
  93. package/{plugins/design/dev-server → apps/studio}/export-dialog.tsx +14 -2
  94. package/{plugins/design/dev-server → apps/studio}/exporters/index.ts +2 -2
  95. package/{plugins/design/dev-server → apps/studio}/http.ts +103 -2
  96. package/{plugins/design/dev-server → apps/studio}/input-router.tsx +8 -1
  97. package/{plugins/design/dev-server → apps/studio}/inspect.ts +20 -3
  98. package/{plugins/design/dev-server → apps/studio}/marquee-overlay.tsx +6 -3
  99. package/{plugins/design/dev-server → apps/studio}/participants-chrome.tsx +1 -1
  100. package/{plugins/design/dev-server → apps/studio}/paths.ts +15 -11
  101. package/{plugins/design/dev-server → apps/studio}/server.ts +19 -1
  102. package/{plugins/design/dev-server → apps/studio}/sync/index.ts +6 -6
  103. package/{plugins/design/dev-server → apps/studio}/sync/materialize.ts +2 -2
  104. package/{plugins/design/dev-server → apps/studio}/test/ai-activity.test.ts +1 -1
  105. package/{plugins/design/dev-server → apps/studio}/test/annotations-api.test.ts +1 -2
  106. package/{plugins/design/dev-server → apps/studio}/test/annotations-layer.test.ts +69 -6
  107. package/{plugins/design/dev-server → apps/studio}/test/annotations-roundtrip.test.ts +98 -1
  108. package/apps/studio/test/annotations-text-format.test.ts +396 -0
  109. package/apps/studio/test/asset-api.test.ts +149 -0
  110. package/{plugins/design/dev-server → apps/studio}/test/boot-self-heal.test.ts +1 -1
  111. package/apps/studio/test/canvas-create-api.test.ts +361 -0
  112. package/{plugins/design/dev-server → apps/studio}/test/canvas-cursors.test.ts +3 -1
  113. package/{plugins/design/dev-server → apps/studio}/test/canvas-edit.test.ts +1 -1
  114. package/{plugins/design/dev-server → apps/studio}/test/canvas-hmr-runtime.test.tsx +1 -1
  115. package/{plugins/design/dev-server → apps/studio}/test/canvas-lib-inline.test.ts +1 -1
  116. package/{plugins/design/dev-server → apps/studio}/test/canvas-lib-resolver.test.ts +2 -3
  117. package/apps/studio/test/canvas-media-drop.test.ts +158 -0
  118. package/{plugins/design/dev-server → apps/studio}/test/canvas-meta-api.test.ts +1 -2
  119. package/{plugins/design/dev-server → apps/studio}/test/canvas-origin-gate.test.ts +18 -3
  120. package/apps/studio/test/canvas-url.test.ts +99 -0
  121. package/{plugins/design/dev-server → apps/studio}/test/collab-annotations-bridge.test.ts +1 -1
  122. package/{plugins/design/dev-server → apps/studio}/test/collab-bridge.test.ts +1 -1
  123. package/{plugins/design/dev-server → apps/studio}/test/collab-room.test.ts +1 -1
  124. package/{plugins/design/dev-server → apps/studio}/test/collab-stress.test.ts +1 -1
  125. package/{plugins/design/dev-server → apps/studio}/test/comments-api.test.ts +1 -2
  126. package/{plugins/design/dev-server → apps/studio}/test/compile-entry.test.ts +1 -2
  127. package/{plugins/design/dev-server → apps/studio}/test/exporters/canva.test.ts +1 -2
  128. package/{plugins/design/dev-server → apps/studio}/test/exporters/history.test.ts +1 -2
  129. package/{plugins/design/dev-server → apps/studio}/test/exporters/pw-launch.test.ts +1 -1
  130. package/{plugins/design/dev-server → apps/studio}/test/exporters/scope.test.ts +1 -2
  131. package/{plugins/design/dev-server → apps/studio}/test/exporters/zip.test.ts +1 -2
  132. package/{plugins/design/dev-server → apps/studio}/test/git-lifecycle.test.ts +1 -2
  133. package/{plugins/design/dev-server → apps/studio}/test/handoff-static-frames.test.ts +1 -1
  134. package/{plugins/design/dev-server → apps/studio}/test/hmr-broadcast.test.ts +1 -1
  135. package/apps/studio/test/index-data-canvas-ds.test.ts +97 -0
  136. package/apps/studio/test/motion-sample.test.ts +35 -0
  137. package/{plugins/design/dev-server → apps/studio}/test/perf-harness.ts +1 -1
  138. package/{plugins/design/dev-server → apps/studio}/test/phase-3.6-smoke.test.ts +3 -1
  139. package/apps/studio/test/read-annotations.test.ts +409 -0
  140. package/{plugins/design/dev-server → apps/studio}/test/runtime-bundle.test.ts +1 -1
  141. package/{plugins/design/dev-server → apps/studio}/test/sanitize-annotation-svg.test.ts +108 -2
  142. package/{plugins/design/dev-server → apps/studio}/test/shared-doc-convergence.test.ts +2 -2
  143. package/{plugins/design/dev-server → apps/studio}/test/shared-doc-migrate.test.ts +2 -3
  144. package/{plugins/design/dev-server → apps/studio}/test/shared-doc-projection.test.ts +1 -1
  145. package/{plugins/design/dev-server → apps/studio}/test/snap-distance-pill.test.ts +1 -1
  146. package/{plugins/design/dev-server → apps/studio}/test/sync-agent.test.ts +2 -3
  147. package/{plugins/design/dev-server → apps/studio}/test/sync-atomic-write.test.ts +2 -3
  148. package/{plugins/design/dev-server → apps/studio}/test/sync-connection-state.test.ts +1 -1
  149. package/{plugins/design/dev-server → apps/studio}/test/sync-echo-guard.test.ts +1 -1
  150. package/{plugins/design/dev-server → apps/studio}/test/sync-fs-mirror.test.ts +1 -2
  151. package/{plugins/design/dev-server → apps/studio}/test/sync-hardening.test.ts +4 -5
  152. package/{plugins/design/dev-server → apps/studio}/test/sync-hubs-config.test.ts +1 -2
  153. package/{plugins/design/dev-server → apps/studio}/test/sync-runtime.test.ts +3 -8
  154. package/{plugins/design/dev-server → apps/studio}/test/sync-status.test.ts +1 -1
  155. package/{plugins/design/dev-server → apps/studio}/test/sync-untrusted.test.ts +1 -2
  156. package/apps/studio/test/tour-overlay.test.tsx +56 -0
  157. package/{plugins/design/dev-server → apps/studio}/test/use-agent-presence.test.tsx +4 -1
  158. package/{plugins/design/dev-server → apps/studio}/test/use-annotation-resize.test.ts +43 -0
  159. package/{plugins/design/dev-server → apps/studio}/test/use-snap-guides.test.ts +1 -1
  160. package/{plugins/design/dev-server → apps/studio}/test/use-tool-mode.test.tsx +4 -1
  161. package/apps/studio/test/whats-new.test.ts +101 -0
  162. package/apps/studio/text-imports.d.ts +9 -0
  163. package/{plugins/design/dev-server → apps/studio}/tool-palette.tsx +4 -69
  164. package/{plugins/design/dev-server → apps/studio}/undo-hud.tsx +1 -1
  165. package/{plugins/design/dev-server → apps/studio}/undo-stack.ts +1 -1
  166. package/{plugins/design/dev-server → apps/studio}/use-agent-presence.tsx +2 -2
  167. package/{plugins/design/dev-server → apps/studio}/use-annotation-resize.tsx +41 -12
  168. package/{plugins/design/dev-server → apps/studio}/use-annotation-selection.tsx +3 -7
  169. package/{plugins/design/dev-server → apps/studio}/use-annotations-visibility.tsx +2 -2
  170. package/{plugins/design/dev-server → apps/studio}/use-artboard-drag.tsx +2 -2
  171. package/{plugins/design/dev-server → apps/studio}/use-canvas-activity.tsx +2 -2
  172. package/apps/studio/use-canvas-media-drop.tsx +343 -0
  173. package/{plugins/design/dev-server → apps/studio}/use-collab.tsx +4 -5
  174. package/{plugins/design/dev-server → apps/studio}/use-cursor-modifiers.tsx +1 -1
  175. package/{plugins/design/dev-server → apps/studio}/use-keyboard-discipline.tsx +5 -2
  176. package/{plugins/design/dev-server → apps/studio}/use-selection-set.tsx +10 -2
  177. package/{plugins/design/dev-server → apps/studio}/use-snap-guides.tsx +1 -1
  178. package/{plugins/design/dev-server → apps/studio}/use-tool-mode.tsx +4 -2
  179. package/{plugins/design/dev-server → apps/studio}/use-undo-stack.tsx +5 -6
  180. package/apps/studio/whats-new.json +139 -0
  181. package/apps/studio/whats-new.schema.json +70 -0
  182. package/apps/studio/whats-new.ts +87 -0
  183. package/cli/bin/maude.mjs +10 -0
  184. package/cli/commands/design.mjs +84 -10
  185. package/cli/commands/design.test.mjs +133 -1
  186. package/cli/commands/help.mjs +8 -2
  187. package/cli/commands/hub.mjs +3 -3
  188. package/cli/commands/hub.test.mjs +1 -1
  189. package/cli/commands/scenario-report.mjs +2 -9
  190. package/cli/lib/cache.mjs +1 -1
  191. package/cli/lib/config-lint.mjs +1 -1
  192. package/cli/lib/copy-tree.mjs +1 -1
  193. package/cli/lib/design-link.mjs +1 -1
  194. package/cli/lib/flow-design-integration.test.mjs +2 -2
  195. package/cli/lib/gitignore-block.mjs +1 -0
  196. package/cli/lib/gitignore-block.test.mjs +2 -2
  197. package/cli/lib/plugin-cli-reachability.test.mjs +8 -7
  198. package/cli/lib/preflight.mjs +1 -1
  199. package/cli/lib/stack-detect.mjs +1 -1
  200. package/package.json +16 -16
  201. package/plugins/design/templates/_shell.html +3 -3
  202. package/plugins/design/templates/brief-board.tsx.template +61 -0
  203. package/plugins/design/templates/canvas.tsx.template +1 -1
  204. package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +18 -3
  205. package/plugins/design/templates/design-system-inspiration/core/config.json.tpl +1 -1
  206. package/plugins/design/templates/design-system-inspiration/core/preview/_motion-readme.md.tpl +1 -1
  207. package/plugins/flow/.claude-plugin/config.schema.json +16 -0
  208. package/plugins/design/dev-server/bin/server-up.sh +0 -135
  209. package/plugins/design/dev-server/dist/client.bundle.js +0 -20692
  210. package/plugins/design/dev-server/dist/comment-mount.js +0 -1924
  211. package/plugins/design/dev-server/dist/runtime/lib0_decoding.js +0 -620
  212. package/plugins/design/dev-server/dist/runtime/lib0_encoding.js +0 -604
  213. package/plugins/design/dev-server/dist/runtime/motion.js +0 -4944
  214. package/plugins/design/dev-server/dist/runtime/motion_react.js +0 -10056
  215. package/plugins/design/dev-server/dist/runtime/pixi-js.js +0 -53104
  216. package/plugins/design/dev-server/dist/runtime/react-dom.js +0 -231
  217. package/plugins/design/dev-server/dist/runtime/react-dom_client.js +0 -10357
  218. package/plugins/design/dev-server/dist/runtime/react.js +0 -535
  219. package/plugins/design/dev-server/dist/runtime/react_jsx-dev-runtime.js +0 -60
  220. package/plugins/design/dev-server/dist/runtime/react_jsx-runtime.js +0 -85
  221. package/plugins/design/dev-server/dist/runtime/y-protocols_awareness.js +0 -376
  222. package/plugins/design/dev-server/dist/runtime/y-protocols_sync.js +0 -100
  223. package/plugins/design/dev-server/dist/runtime/yjs.js +0 -6687
  224. package/plugins/design/dev-server/dist/styles.css +0 -2710
  225. package/{plugins/design/dev-server → apps/studio}/activity.ts +0 -0
  226. package/{plugins/design/dev-server → apps/studio}/bin/_enumerate-artboards-playwright.mjs +0 -0
  227. package/{plugins/design/dev-server → apps/studio}/bin/_html-playwright.mjs +0 -0
  228. package/{plugins/design/dev-server → apps/studio}/bin/_pdf-playwright.mjs +0 -0
  229. package/{plugins/design/dev-server → apps/studio}/bin/_png-playwright.mjs +0 -0
  230. package/{plugins/design/dev-server → apps/studio}/bin/_pptx-playwright.mjs +0 -0
  231. package/{plugins/design/dev-server → apps/studio}/bin/_pw-launch.mjs +0 -0
  232. package/{plugins/design/dev-server → apps/studio}/bin/_screenshot-playwright.mjs +0 -0
  233. package/{plugins/design/dev-server → apps/studio}/bin/_svg-optimize.mjs +0 -0
  234. package/{plugins/design/dev-server → apps/studio}/bin/_svg-playwright.mjs +0 -0
  235. package/{plugins/design/dev-server → apps/studio}/bin/asset-sweep.sh +0 -0
  236. package/{plugins/design/dev-server → apps/studio}/bin/bootstrap-check.sh +0 -0
  237. package/{plugins/design/dev-server → apps/studio}/bin/check-runtime-bundles.sh +0 -0
  238. package/{plugins/design/dev-server → apps/studio}/bin/draw-build.sh +0 -0
  239. package/{plugins/design/dev-server → apps/studio}/bin/handoff.sh +0 -0
  240. package/{plugins/design/dev-server → apps/studio}/bin/prep.sh +0 -0
  241. package/{plugins/design/dev-server → apps/studio}/bin/slug.sh +0 -0
  242. package/{plugins/design/dev-server → apps/studio}/bin/smoke.sh +0 -0
  243. package/{plugins/design/dev-server → apps/studio}/bin/svg-optimize.sh +0 -0
  244. package/{plugins/design/dev-server → apps/studio}/boot-self-heal.ts +0 -0
  245. package/{plugins/design/dev-server → apps/studio}/canvas-build.ts +0 -0
  246. package/{plugins/design/dev-server → apps/studio}/canvas-edit.ts +0 -0
  247. package/{plugins/design/dev-server → apps/studio}/canvas-header.ts +0 -0
  248. package/{plugins/design/dev-server → apps/studio}/canvas-meta.schema.json +0 -0
  249. package/{plugins/design/dev-server → apps/studio}/canvas-pipeline.ts +0 -0
  250. package/{plugins/design/dev-server → apps/studio}/client/hmr.mjs +0 -0
  251. package/{plugins/design/dev-server → apps/studio}/client/iframe-lazy.mjs +0 -0
  252. package/{plugins/design/dev-server → apps/studio}/client/styles/0-reset.css +0 -0
  253. package/{plugins/design/dev-server → apps/studio}/client/styles/1-tokens.css +0 -0
  254. package/{plugins/design/dev-server → apps/studio}/client/styles/2-layout.css +0 -0
  255. package/{plugins/design/dev-server → apps/studio}/client/styles/5-utilities.css +0 -0
  256. package/{plugins/design/dev-server → apps/studio}/collab/ai-activity.ts +0 -0
  257. package/{plugins/design/dev-server → apps/studio}/collab/awareness-bridge.ts +1 -1
  258. package/{plugins/design/dev-server → apps/studio}/collab/git-lifecycle.ts +0 -0
  259. package/{plugins/design/dev-server → apps/studio}/collab/protocol.ts +0 -0
  260. package/{plugins/design/dev-server → apps/studio}/collab/registry.ts +0 -0
  261. package/{plugins/design/dev-server → apps/studio}/collab/room.ts +0 -0
  262. package/{plugins/design/dev-server → apps/studio}/config.schema.json +0 -0
  263. package/{plugins/design/dev-server → apps/studio}/context.ts +0 -0
  264. package/{plugins/design/dev-server → apps/studio}/dist/runtime/.min-sizes.json +0 -0
  265. package/{plugins/design/dev-server → apps/studio}/draw/test/brush.test.ts +0 -0
  266. package/{plugins/design/dev-server → apps/studio}/draw/test/composition.test.ts +0 -0
  267. package/{plugins/design/dev-server → apps/studio}/draw/test/geometry.test.ts +1 -1
  268. package/{plugins/design/dev-server → apps/studio}/draw/test/gradient.test.ts +0 -0
  269. package/{plugins/design/dev-server → apps/studio}/draw/test/optimize.test.ts +0 -0
  270. package/{plugins/design/dev-server → apps/studio}/draw/test/palette.test.ts +0 -0
  271. package/{plugins/design/dev-server → apps/studio}/draw/test/primitives.test.ts +1 -1
  272. package/{plugins/design/dev-server → apps/studio}/examples/README.md +0 -0
  273. package/{plugins/design/dev-server → apps/studio}/exporters/_browser-bundles.ts +0 -0
  274. package/{plugins/design/dev-server → apps/studio}/exporters/canva-handoff-prompt.ts +0 -0
  275. package/{plugins/design/dev-server → apps/studio}/exporters/canva.ts +0 -0
  276. package/{plugins/design/dev-server → apps/studio}/exporters/html.ts +1 -1
  277. package/{plugins/design/dev-server → apps/studio}/exporters/pdf.ts +1 -1
  278. package/{plugins/design/dev-server → apps/studio}/exporters/png.ts +1 -1
  279. package/{plugins/design/dev-server → apps/studio}/exporters/pptx.ts +1 -1
  280. package/{plugins/design/dev-server → apps/studio}/exporters/scope.ts +0 -0
  281. package/{plugins/design/dev-server → apps/studio}/exporters/svg.ts +1 -1
  282. package/{plugins/design/dev-server → apps/studio}/exporters/zip.ts +0 -0
  283. package/{plugins/design/dev-server → apps/studio}/fs-watch.ts +0 -0
  284. package/{plugins/design/dev-server → apps/studio}/handoff.ts +0 -0
  285. package/{plugins/design/dev-server → apps/studio}/history.ts +0 -0
  286. package/{plugins/design/dev-server → apps/studio}/hmr-broadcast.ts +0 -0
  287. package/{plugins/design/dev-server → apps/studio}/locator.ts +0 -0
  288. package/{plugins/design/dev-server → apps/studio}/mem.ts +0 -0
  289. package/{plugins/design/dev-server → apps/studio}/runtime-bundle.ts +0 -0
  290. package/{plugins/design/dev-server → apps/studio}/server.mjs +0 -0
  291. package/{plugins/design/dev-server → apps/studio}/sync/agent.ts +1 -1
  292. package/{plugins/design/dev-server → apps/studio}/sync/atomic-write.ts +0 -0
  293. package/{plugins/design/dev-server → apps/studio}/sync/codec.ts +0 -0
  294. package/{plugins/design/dev-server → apps/studio}/sync/connection-state.ts +0 -0
  295. package/{plugins/design/dev-server → apps/studio}/sync/echo-guard.ts +0 -0
  296. package/{plugins/design/dev-server → apps/studio}/sync/fs-mirror.ts +0 -0
  297. package/{plugins/design/dev-server → apps/studio}/sync/hubs-config.ts +0 -0
  298. package/{plugins/design/dev-server → apps/studio}/sync/migrate-seed.ts +1 -1
  299. package/{plugins/design/dev-server → apps/studio}/sync/origins.ts +0 -0
  300. package/{plugins/design/dev-server → apps/studio}/sync/projection.ts +3 -3
  301. package/{plugins/design/dev-server → apps/studio}/sync/status.ts +0 -0
  302. package/{plugins/design/dev-server → apps/studio}/sync/untrusted.ts +0 -0
  303. package/{plugins/design/dev-server → apps/studio}/test/_helpers.ts +0 -0
  304. package/{plugins/design/dev-server → apps/studio}/test/active-state.test.ts +0 -0
  305. package/{plugins/design/dev-server → apps/studio}/test/activity.test.ts +0 -0
  306. package/{plugins/design/dev-server → apps/studio}/test/annotation-strokes-command.test.ts +0 -0
  307. package/{plugins/design/dev-server → apps/studio}/test/annotations-draw-modifiers.test.ts +2 -2
  308. package/{plugins/design/dev-server → apps/studio}/test/artboard-activity-overlay.test.tsx +0 -0
  309. package/{plugins/design/dev-server → apps/studio}/test/binary-smoke.test.ts +0 -0
  310. package/{plugins/design/dev-server → apps/studio}/test/bundle-smoke.test.ts +0 -0
  311. package/{plugins/design/dev-server → apps/studio}/test/canvas-build.test.ts +0 -0
  312. package/{plugins/design/dev-server → apps/studio}/test/canvas-header.test.ts +1 -1
  313. package/{plugins/design/dev-server → apps/studio}/test/canvas-lib-motion.test.ts +0 -0
  314. package/{plugins/design/dev-server → apps/studio}/test/canvas-pipeline.test.ts +0 -0
  315. package/{plugins/design/dev-server → apps/studio}/test/canvas-route.test.ts +0 -0
  316. package/{plugins/design/dev-server → apps/studio}/test/collab-awareness-bridge.test.ts +0 -0
  317. package/{plugins/design/dev-server → apps/studio}/test/collab-loopback.test.ts +0 -0
  318. package/{plugins/design/dev-server → apps/studio}/test/collab-protocol.test.ts +2 -2
  319. package/{plugins/design/dev-server → apps/studio}/test/collab-reseed-guard.test.ts +0 -0
  320. package/{plugins/design/dev-server → apps/studio}/test/comment-mount.test.ts +0 -0
  321. package/{plugins/design/dev-server → apps/studio}/test/context-resolve-tokens.test.ts +0 -0
  322. package/{plugins/design/dev-server → apps/studio}/test/csp-canvas-shell.test.ts +0 -0
  323. package/{plugins/design/dev-server → apps/studio}/test/equal-spacing-detector.test.ts +0 -0
  324. package/{plugins/design/dev-server → apps/studio}/test/exporters/endpoint.test.ts +0 -0
  325. package/{plugins/design/dev-server → apps/studio}/test/exporters/html.test.ts +0 -0
  326. package/{plugins/design/dev-server → apps/studio}/test/exporters/pdf.test.ts +0 -0
  327. package/{plugins/design/dev-server → apps/studio}/test/exporters/png.test.ts +0 -0
  328. package/{plugins/design/dev-server → apps/studio}/test/exporters/pptx-deck.test.ts +0 -0
  329. package/{plugins/design/dev-server → apps/studio}/test/exporters/pptx.test.ts +0 -0
  330. package/{plugins/design/dev-server → apps/studio}/test/exporters/svg.test.ts +0 -0
  331. package/{plugins/design/dev-server → apps/studio}/test/fixtures/phase-20-annotations.svg +0 -0
  332. package/{plugins/design/dev-server → apps/studio}/test/fixtures/phase-21-annotations.svg +0 -0
  333. package/{plugins/design/dev-server → apps/studio}/test/fs-watch.test.ts +0 -0
  334. package/{plugins/design/dev-server → apps/studio}/test/handoff.test.ts +1 -1
  335. package/{plugins/design/dev-server → apps/studio}/test/history-rollback.test.ts +0 -0
  336. package/{plugins/design/dev-server → apps/studio}/test/hmr-classify.test.ts +0 -0
  337. package/{plugins/design/dev-server → apps/studio}/test/input-router.test.ts +2 -2
  338. package/{plugins/design/dev-server → apps/studio}/test/locator.test.ts +1 -1
  339. package/{plugins/design/dev-server → apps/studio}/test/marquee-overlay.test.ts +0 -0
  340. package/{plugins/design/dev-server → apps/studio}/test/move-artboards-command.test.ts +0 -0
  341. package/{plugins/design/dev-server → apps/studio}/test/participants-chrome.test.ts +0 -0
  342. package/{plugins/design/dev-server → apps/studio}/test/paths.test.ts +0 -0
  343. package/{plugins/design/dev-server → apps/studio}/test/runtime-bundle-error-mapping.test.ts +0 -0
  344. package/{plugins/design/dev-server → apps/studio}/test/server-lifecycle.test.ts +0 -0
  345. package/{plugins/design/dev-server → apps/studio}/test/shared-doc-foundation.test.ts +0 -0
  346. package/{plugins/design/dev-server → apps/studio}/test/sync-codec.test.ts +1 -1
  347. package/{plugins/design/dev-server → apps/studio}/test/sync-meta-codec.test.ts +1 -1
  348. package/{plugins/design/dev-server → apps/studio}/test/system-endpoint.test.ts +0 -0
  349. package/{plugins/design/dev-server → apps/studio}/test/tool-palette.test.tsx +0 -0
  350. package/{plugins/design/dev-server → apps/studio}/test/undo-stack.test.ts +2 -2
  351. package/{plugins/design/dev-server → apps/studio}/test/use-annotation-selection.test.tsx +0 -0
  352. package/{plugins/design/dev-server → apps/studio}/test/use-artboard-drag.test.ts +3 -3
  353. package/{plugins/design/dev-server → apps/studio}/test/use-canvas-activity.test.tsx +2 -2
  354. package/{plugins/design/dev-server → apps/studio}/test/use-collab.test.ts +0 -0
  355. package/{plugins/design/dev-server → apps/studio}/test/use-cursor-modifiers.test.ts +0 -0
  356. package/{plugins/design/dev-server → apps/studio}/test/use-keyboard-discipline.test.ts +0 -0
  357. package/{plugins/design/dev-server → apps/studio}/test/use-selection-set.test.tsx +1 -1
  358. package/{plugins/design/dev-server → apps/studio}/test/use-undo-stack.test.tsx +2 -2
  359. /package/{plugins/design/dev-server → apps/studio}/test/ws-handshake.test.ts +0 -0
  360. /package/{plugins/design/dev-server → apps/studio}/tsconfig.json +0 -0
  361. /package/{plugins/design/dev-server → apps/studio}/ws.ts +0 -0
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file use-annotation-selection.tsx — Phase 5.1 annotation selection store
3
- * @scope plugins/design/dev-server/use-annotation-selection.tsx
3
+ * @scope apps/studio/use-annotation-selection.tsx
4
4
  * @purpose Parallel to `use-selection-set`, but holds annotation stroke IDs
5
5
  * instead of DOM-element selections. Annotation IDs are local
6
6
  * identifiers (`s_*`) generated by `rid()` — they never live in
@@ -10,7 +10,7 @@
10
10
  * selection resets (the strokes themselves persist via `.annotations.svg`).
11
11
  */
12
12
 
13
- import { type ReactNode, createContext, useCallback, useContext, useMemo, useState } from 'react';
13
+ import { createContext, type ReactNode, useCallback, useContext, useMemo, useState } from 'react';
14
14
 
15
15
  export interface AnnotationSelectionValue {
16
16
  selectedIds: string[];
@@ -34,11 +34,7 @@ function dedupe(ids: string[]): string[] {
34
34
  return out;
35
35
  }
36
36
 
37
- export function AnnotationSelectionProvider({
38
- children,
39
- }: {
40
- children: ReactNode;
41
- }) {
37
+ export function AnnotationSelectionProvider({ children }: { children: ReactNode }) {
42
38
  const [selectedIds, setSelectedIds] = useState<string[]>([]);
43
39
 
44
40
  const replace = useCallback((id: string | string[]) => {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file use-annotations-visibility.tsx — Phase 5.1 visibility store
3
- * @scope plugins/design/dev-server/use-annotations-visibility.tsx
3
+ * @scope apps/studio/use-annotations-visibility.tsx
4
4
  * @purpose Tiny boolean store shared by AnnotationsLayer (render gate),
5
5
  * ToolPalette (presentation toggle button), and the menubar
6
6
  * bridge (`view-annotations` postMessage handler in
@@ -8,7 +8,7 @@
8
8
  * siblings under CanvasRouter all consume the same value.
9
9
  */
10
10
 
11
- import { type ReactNode, createContext, useCallback, useContext, useMemo, useState } from 'react';
11
+ import { createContext, type ReactNode, useCallback, useContext, useMemo, useState } from 'react';
12
12
 
13
13
  export interface AnnotationsVisibilityValue {
14
14
  visible: boolean;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file use-artboard-drag.tsx — Phase 4.2 artboard drag controller
3
- * @scope plugins/design/dev-server/use-artboard-drag.tsx
3
+ * @scope apps/studio/use-artboard-drag.tsx
4
4
  * @purpose Owns the pointerdown → pointermove → pointerup state machine
5
5
  * for dragging artboards on the infinite canvas. The pure
6
6
  * `dragReducer` + `commitFromState` are unit-testable without a
@@ -38,7 +38,7 @@ import {
38
38
 
39
39
  import { DRAG_THRESHOLD_PX as INPUT_DRAG_THRESHOLD_PX } from './input-router.tsx';
40
40
  import type { Selection } from './use-selection-set.tsx';
41
- import { type Rect, type SnapResult, computeSnap } from './use-snap-guides.tsx';
41
+ import { computeSnap, type Rect, type SnapResult } from './use-snap-guides.tsx';
42
42
 
43
43
  // ─────────────────────────────────────────────────────────────────────────────
44
44
  // Constants
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file use-canvas-activity.tsx — Phase 13 / DDR-029 activity context.
3
- * @scope plugins/design/dev-server/use-canvas-activity.tsx
3
+ * @scope apps/studio/use-canvas-activity.tsx
4
4
  * @purpose Iframe-runtime React context fed by the server's `activity` WS
5
5
  * messages. `DCArtboard` reads it to render the "agent works here"
6
6
  * overlay on the artboards being edited right now.
@@ -14,8 +14,8 @@
14
14
  */
15
15
 
16
16
  import {
17
- type ReactNode,
18
17
  createContext,
18
+ type ReactNode,
19
19
  useContext,
20
20
  useEffect,
21
21
  useMemo,
@@ -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
@@ -16,9 +16,11 @@
16
16
  * <CollabProvider> never resolve these specifiers and pay zero bundle cost.
17
17
  */
18
18
 
19
+ import * as decoding from 'lib0/decoding';
20
+ import * as encoding from 'lib0/encoding';
19
21
  import {
20
- type ReactNode,
21
22
  createContext,
23
+ type ReactNode,
22
24
  useCallback,
23
25
  useContext,
24
26
  useEffect,
@@ -26,9 +28,6 @@ import {
26
28
  useRef,
27
29
  useState,
28
30
  } from 'react';
29
-
30
- import * as decoding from 'lib0/decoding';
31
- import * as encoding from 'lib0/encoding';
32
31
  import { Awareness, applyAwarenessUpdate, encodeAwarenessUpdate } from 'y-protocols/awareness';
33
32
  import { readSyncMessage, writeSyncStep1, writeUpdate } from 'y-protocols/sync';
34
33
  import * as Y from 'yjs';
@@ -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
@@ -17,8 +17,8 @@
17
17
  */
18
18
 
19
19
  import {
20
- type ReactNode,
21
20
  createContext,
21
+ type ReactNode,
22
22
  useCallback,
23
23
  useContext,
24
24
  useEffect,
@@ -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
@@ -12,8 +12,8 @@
12
12
  */
13
13
 
14
14
  import {
15
- type ReactNode,
16
15
  createContext,
16
+ type ReactNode,
17
17
  useCallback,
18
18
  useContext,
19
19
  useEffect,
@@ -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,
@@ -18,8 +18,8 @@
18
18
  */
19
19
 
20
20
  import {
21
- type ReactNode,
22
21
  createContext,
22
+ type ReactNode,
23
23
  useCallback,
24
24
  useContext,
25
25
  useEffect,
@@ -31,13 +31,13 @@ import {
31
31
  import {
32
32
  type CommandRecord,
33
33
  type CommandSinks,
34
- type EditCommand,
35
- type UndoStackState,
36
34
  canRedo as canRedoOf,
37
35
  canUndo as canUndoOf,
36
+ type EditCommand,
38
37
  loadStackState,
39
38
  rebuildCommand,
40
39
  saveStackState,
40
+ type UndoStackState,
41
41
  undoReducer,
42
42
  } from './undo-stack.ts';
43
43
 
@@ -349,7 +349,6 @@ export function useUndoSinks(): UndoSinksValue {
349
349
  return useContext(UndoSinksContext) ?? NOOP_SINKS;
350
350
  }
351
351
 
352
- export { UndoStackContext, UndoSinksContext };
353
-
354
352
  // Re-export the EditCommand type for ergonomic single-import in consumers.
355
353
  export type { CommandRecord, EditCommand } from './undo-stack.ts';
354
+ export { UndoSinksContext, UndoStackContext };