@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,191 @@
1
+ // overlay.jsx — hand-rolled guided-tour engine (zero runtime dep, DDR-B).
2
+ //
3
+ // One <TourOverlay steps={[…]} open onClose onComplete/> drives both the
4
+ // per-feature spotlight tours (a What's New entry's `tour[]`) and the evergreen
5
+ // usage tour. A step = { target (CSS selector / [data-tour] anchor), title,
6
+ // body, placement? }. A step whose target can't be resolved still shows, just
7
+ // centered with no spotlight — the tour never dead-ends on a missing element.
8
+ //
9
+ // A11y: role="dialog" + aria-modal, focus moves to the primary button on each
10
+ // step and is trapped within the card, focus is restored on close, Esc skips,
11
+ // ←/→ navigate. prefers-reduced-motion disables the spotlight transition.
12
+
13
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
14
+
15
+ function getRect(target) {
16
+ if (typeof document === 'undefined' || !target) return null;
17
+ try {
18
+ const el = document.querySelector(target);
19
+ if (!el) return null;
20
+ const r = el.getBoundingClientRect();
21
+ if (r.width === 0 && r.height === 0) return null;
22
+ return r;
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
28
+ const CARD_W = 320;
29
+ const CARD_H_EST = 168;
30
+
31
+ function computeCardStyle(rect) {
32
+ if (typeof window === 'undefined' || !rect) {
33
+ return { top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: CARD_W };
34
+ }
35
+ const vw = window.innerWidth;
36
+ const vh = window.innerHeight;
37
+ const roomBelow = vh - rect.bottom;
38
+ const top =
39
+ roomBelow > CARD_H_EST + 24 ? rect.bottom + 12 : Math.max(12, rect.top - CARD_H_EST - 12);
40
+ const left = Math.min(Math.max(12, rect.left), vw - CARD_W - 12);
41
+ return { top, left, width: CARD_W };
42
+ }
43
+
44
+ export function TourOverlay({ steps, open, onClose, onComplete }) {
45
+ const [i, setI] = useState(0);
46
+ const [rect, setRect] = useState(null);
47
+ const cardRef = useRef(null);
48
+ const prevFocus = useRef(null);
49
+
50
+ const measure = useCallback(() => {
51
+ const step = steps[i];
52
+ setRect(step ? getRect(step.target) : null);
53
+ }, [steps, i]);
54
+
55
+ // Reset to the first step whenever a tour (re)opens.
56
+ useEffect(() => {
57
+ if (open) setI(0);
58
+ }, [open]);
59
+
60
+ useLayoutEffect(() => {
61
+ if (open) measure();
62
+ }, [open, measure]);
63
+
64
+ // Keep the spotlight glued to its target through scroll/resize; restore focus.
65
+ useEffect(() => {
66
+ if (!open) return;
67
+ prevFocus.current = document.activeElement;
68
+ const onMove = () => measure();
69
+ window.addEventListener('resize', onMove);
70
+ window.addEventListener('scroll', onMove, true);
71
+ return () => {
72
+ window.removeEventListener('resize', onMove);
73
+ window.removeEventListener('scroll', onMove, true);
74
+ try {
75
+ prevFocus.current?.focus?.();
76
+ } catch {}
77
+ };
78
+ }, [open, measure]);
79
+
80
+ const last = i >= steps.length - 1;
81
+ const next = useCallback(() => {
82
+ if (last) {
83
+ onComplete?.();
84
+ onClose?.();
85
+ } else {
86
+ setI((n) => Math.min(n + 1, steps.length - 1));
87
+ }
88
+ }, [last, onComplete, onClose, steps.length]);
89
+ const back = useCallback(() => setI((n) => Math.max(0, n - 1)), []);
90
+
91
+ // Keyboard: Esc skips, ←/→ navigate, Tab is trapped within the card buttons.
92
+ useEffect(() => {
93
+ if (!open) return;
94
+ function onKey(e) {
95
+ if (e.key === 'Escape') {
96
+ e.preventDefault();
97
+ onClose?.();
98
+ } else if (e.key === 'ArrowRight') {
99
+ e.preventDefault();
100
+ next();
101
+ } else if (e.key === 'ArrowLeft') {
102
+ e.preventDefault();
103
+ back();
104
+ } else if (e.key === 'Tab') {
105
+ const card = cardRef.current;
106
+ if (!card) return;
107
+ const f = card.querySelectorAll('button');
108
+ if (!f.length) return;
109
+ const first = f[0];
110
+ const lastEl = f[f.length - 1];
111
+ if (e.shiftKey && document.activeElement === first) {
112
+ e.preventDefault();
113
+ lastEl.focus();
114
+ } else if (!e.shiftKey && document.activeElement === lastEl) {
115
+ e.preventDefault();
116
+ first.focus();
117
+ }
118
+ }
119
+ }
120
+ window.addEventListener('keydown', onKey, true);
121
+ return () => window.removeEventListener('keydown', onKey, true);
122
+ }, [open, next, back, onClose]);
123
+
124
+ // Move focus to the primary action on each step (accessible dialog behavior).
125
+ useEffect(() => {
126
+ if (!open) return;
127
+ const t = setTimeout(() => {
128
+ const primary = cardRef.current?.querySelector('[data-tour-primary]');
129
+ try {
130
+ primary?.focus();
131
+ } catch {}
132
+ }, 0);
133
+ return () => clearTimeout(t);
134
+ }, [open, i]);
135
+
136
+ if (!open || !steps.length) return null;
137
+ const step = steps[i];
138
+ const pad = 6;
139
+ const spot = rect
140
+ ? {
141
+ top: rect.top - pad,
142
+ left: rect.left - pad,
143
+ width: rect.width + pad * 2,
144
+ height: rect.height + pad * 2,
145
+ }
146
+ : null;
147
+
148
+ return (
149
+ <div className="mdcc-tour" role="presentation">
150
+ {spot ? (
151
+ <div className="mdcc-tour__spot" style={spot} aria-hidden="true" />
152
+ ) : (
153
+ <div className="mdcc-tour__scrim" aria-hidden="true" />
154
+ )}
155
+ <div
156
+ className="mdcc-tour__card"
157
+ ref={cardRef}
158
+ role="dialog"
159
+ aria-modal="true"
160
+ aria-labelledby="mdcc-tour-title"
161
+ aria-describedby="mdcc-tour-body"
162
+ style={computeCardStyle(rect)}
163
+ >
164
+ <div className="mdcc-tour__step">
165
+ {i + 1} / {steps.length}
166
+ </div>
167
+ <div className="mdcc-tour__title" id="mdcc-tour-title">
168
+ {step.title}
169
+ </div>
170
+ <div className="mdcc-tour__body" id="mdcc-tour-body">
171
+ {step.body}
172
+ </div>
173
+ <div className="mdcc-tour__actions">
174
+ <button type="button" className="mdcc-tour__skip" onClick={onClose}>
175
+ Skip
176
+ </button>
177
+ <div className="mdcc-tour__nav">
178
+ {i > 0 && (
179
+ <button type="button" className="mdcc-tour__back" onClick={back}>
180
+ Back
181
+ </button>
182
+ )}
183
+ <button type="button" className="mdcc-tour__next" data-tour-primary onClick={next}>
184
+ {last ? 'Done' : 'Next'}
185
+ </button>
186
+ </div>
187
+ </div>
188
+ </div>
189
+ </div>
190
+ );
191
+ }
@@ -0,0 +1,35 @@
1
+ // usage-tour.js — the evergreen "how the Maude UI works" walkthrough.
2
+ //
3
+ // Targets stable elements that exist at first load (no canvas open required):
4
+ // the sidebar, the empty viewport, the menus, the What's New badge, and Help.
5
+ // Steps whose target is missing are shown centered by the overlay, so this is
6
+ // resilient to chrome changes. Reached from Help → "Take the tour" and offered
7
+ // once on first run.
8
+
9
+ export const USAGE_TOUR = [
10
+ {
11
+ target: '.sidebar',
12
+ title: 'Your canvases live here',
13
+ body: 'Every mock in the project shows up in this tree. Click one to open it in a tab — or use “+ board” to spin up a blank brief-board, no command needed.',
14
+ },
15
+ {
16
+ target: '.viewport',
17
+ title: 'The canvas',
18
+ body: 'Open a canvas, then ⌘-hover to preview the element under your cursor and ⌘-click to select it. Right-click for Copy CSS / Fit / Reset.',
19
+ },
20
+ {
21
+ target: '.mb-menus',
22
+ title: 'Menus & tools',
23
+ body: 'View toggles panels (tree, comments, annotations); Selection and Tools act on the active canvas. Press ? for every shortcut.',
24
+ },
25
+ {
26
+ target: "[data-tour='whatsnew']",
27
+ title: 'What’s new',
28
+ body: 'New features land here. Click the ✦ badge any time to catch up on what shipped.',
29
+ },
30
+ {
31
+ target: "[data-tour='help']",
32
+ title: 'Help is one key away',
33
+ body: 'Press ? for the full keyboard cheat-sheet — and you can restart this tour from the Help menu whenever you like.',
34
+ },
35
+ ];
@@ -0,0 +1,52 @@
1
+ // whats-new-seen.js — pure, browser-safe "what have I already seen?" logic.
2
+ //
3
+ // No React, no DOM, no node imports — so it's bundled into the client AND
4
+ // imported directly by bun:test (test/whats-new.test.ts). Keep it that way.
5
+
6
+ /** Parse a semver-ish string into a [major, minor, patch] triple, or null. */
7
+ export function parseVer(v) {
8
+ if (typeof v !== 'string') return null;
9
+ const m = v.match(/^(\d+)\.(\d+)\.(\d+)/);
10
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
11
+ }
12
+
13
+ /**
14
+ * Is version `a` strictly newer than `b`?
15
+ * - Unparseable `a` (e.g. "dev") → not newer (false).
16
+ * - Unparseable `b` but parseable `a` → newer (true): a real release outranks
17
+ * a non-version "seen" marker, so the entry surfaces.
18
+ */
19
+ export function verGt(a, b) {
20
+ const pa = parseVer(a);
21
+ const pb = parseVer(b);
22
+ if (!pa) return false;
23
+ if (!pb) return true;
24
+ for (let i = 0; i < 3; i++) {
25
+ if (pa[i] !== pb[i]) return pa[i] > pb[i];
26
+ }
27
+ return false;
28
+ }
29
+
30
+ /**
31
+ * Entries the user hasn't seen yet, given the version they last acknowledged.
32
+ * A pending entry (version == null) is always unseen until acknowledged.
33
+ */
34
+ export function computeUnseen(entries, seenVersion) {
35
+ if (!Array.isArray(entries)) return [];
36
+ return entries.filter((e) => e && (e.version == null || verGt(e.version, seenVersion)));
37
+ }
38
+
39
+ /**
40
+ * The version to persist as "seen" after the user reads the panel — the highest
41
+ * of the current feed version and every entry's version. Stamping this clears
42
+ * the unseen set (pending entries excepted — they have no version to outrank).
43
+ */
44
+ export function bestSeenVersion(entries, currentVersion) {
45
+ let best = currentVersion;
46
+ if (Array.isArray(entries)) {
47
+ for (const e of entries) {
48
+ if (e && e.version && verGt(e.version, best)) best = e.version;
49
+ }
50
+ }
51
+ return best;
52
+ }
@@ -0,0 +1,250 @@
1
+ // whats-new.jsx — Maude UI "What's New" surfaces.
2
+ //
3
+ // useWhatsNew(version) → headless handle (fetch + seen-state + open-state)
4
+ // <WhatsNewBadge> → menubar entry point (✦ + unseen-count dot)
5
+ // <WhatsNewToast> → first-run notice (bottom-left, mirrors ai-banner.tsx)
6
+ // <WhatsNewPanel> → list of entries (reuses the help-modal backdrop)
7
+ //
8
+ // Styling lives in client/styles/4-components.css (.mdcc-wn-*). Spotlight tours
9
+ // (entry.tour[]) land in Phase 3 — the panel/toast leave a hook for them.
10
+
11
+ import { useCallback, useEffect, useState } from 'react';
12
+ import { bestSeenVersion, computeUnseen } from './whats-new-seen.js';
13
+
14
+ const WN_SEEN = 'mdcc-whatsnew-seen';
15
+ const WN_TOAST = 'mdcc-whatsnew-toast-dismissed';
16
+
17
+ // learnMore renders as <a href> in the privileged main origin. Only bind an
18
+ // http(s) URL — belt-and-suspenders next to React's built-in URL sanitizer and
19
+ // the schema's pattern (security review, defense-in-depth).
20
+ function isSafeHref(url) {
21
+ return typeof url === 'string' && /^https?:\/\//i.test(url);
22
+ }
23
+
24
+ function readSeen(currentVersion) {
25
+ try {
26
+ const v = localStorage.getItem(WN_SEEN);
27
+ if (v) return v;
28
+ // First ever run: acknowledge the current version silently so a brand-new
29
+ // user isn't greeted with the entire backlog as "new". Only true upgrades
30
+ // (and pending dev entries) surface after this.
31
+ localStorage.setItem(WN_SEEN, currentVersion);
32
+ } catch {}
33
+ return currentVersion;
34
+ }
35
+
36
+ function readToastDismissed() {
37
+ try {
38
+ return localStorage.getItem(WN_TOAST);
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ export function useWhatsNew(currentVersion) {
45
+ const [entries, setEntries] = useState([]);
46
+ const [feedVersion, setFeedVersion] = useState(currentVersion);
47
+ const [seenVersion, setSeenVersion] = useState(() => readSeen(currentVersion));
48
+ const [toastDismissed, setToastDismissed] = useState(() => readToastDismissed());
49
+ const [panelOpen, setPanelOpen] = useState(false);
50
+
51
+ useEffect(() => {
52
+ let cancelled = false;
53
+ fetch('/_api/whats-new')
54
+ .then((r) => r.json())
55
+ .then((j) => {
56
+ if (cancelled) return;
57
+ setEntries(Array.isArray(j?.entries) ? j.entries : []);
58
+ if (typeof j?.version === 'string') setFeedVersion(j.version);
59
+ })
60
+ .catch(() => {
61
+ /* offline / restart — no banner */
62
+ });
63
+ return () => {
64
+ cancelled = true;
65
+ };
66
+ }, []);
67
+
68
+ const unseen = computeUnseen(entries, seenVersion);
69
+ const showToast = unseen.length > 0 && toastDismissed !== feedVersion && !panelOpen;
70
+ const toastEntry = unseen[0] ?? null;
71
+
72
+ const markAllSeen = useCallback(() => {
73
+ const next = bestSeenVersion(entries, feedVersion);
74
+ setSeenVersion(next);
75
+ setToastDismissed(feedVersion);
76
+ try {
77
+ localStorage.setItem(WN_SEEN, next);
78
+ localStorage.setItem(WN_TOAST, feedVersion);
79
+ } catch {}
80
+ }, [entries, feedVersion]);
81
+
82
+ const dismissToast = useCallback(() => {
83
+ setToastDismissed(feedVersion);
84
+ try {
85
+ localStorage.setItem(WN_TOAST, feedVersion);
86
+ } catch {}
87
+ }, [feedVersion]);
88
+
89
+ const openPanel = useCallback(() => setPanelOpen(true), []);
90
+ const closePanel = useCallback(() => {
91
+ setPanelOpen(false);
92
+ markAllSeen();
93
+ }, [markAllSeen]);
94
+
95
+ return {
96
+ entries,
97
+ unseen,
98
+ feedVersion,
99
+ panelOpen,
100
+ openPanel,
101
+ closePanel,
102
+ showToast,
103
+ toastEntry,
104
+ dismissToast,
105
+ markAllSeen,
106
+ };
107
+ }
108
+
109
+ export function WhatsNewBadge({ count = 0, onOpen }) {
110
+ return (
111
+ <button
112
+ type="button"
113
+ className="mdcc-wn-badge"
114
+ data-tour="whatsnew"
115
+ onClick={onOpen}
116
+ title="What's new"
117
+ aria-label={count > 0 ? `What's new — ${count} new` : "What's new"}
118
+ >
119
+ <span aria-hidden="true">✦</span>
120
+ <span className="mdcc-wn-badge__label">New</span>
121
+ {count > 0 && (
122
+ <span className="mdcc-wn-badge__dot" aria-hidden="true">
123
+ {count}
124
+ </span>
125
+ )}
126
+ </button>
127
+ );
128
+ }
129
+
130
+ export function WhatsNewToast({ wn }) {
131
+ if (!wn.showToast || !wn.toastEntry) return null;
132
+ const e = wn.toastEntry;
133
+ const total = wn.unseen.length;
134
+ // Plan C P6 — restyled to the maude `.st-toast` family (was `mdcc-wn-toast`),
135
+ // matching `.design/ui/Studio.tsx` AB-D. Behavior unchanged.
136
+ return (
137
+ <div className="st-toast" role="status" aria-live="polite">
138
+ <button
139
+ type="button"
140
+ className="st-toast-close"
141
+ aria-label="Dismiss"
142
+ onClick={wn.dismissToast}
143
+ >
144
+ ×
145
+ </button>
146
+ <div className="st-toast-hd">
147
+ <span aria-hidden="true">✦</span>
148
+ What's new{e.version ? ` · v${e.version}` : ''}
149
+ </div>
150
+ <div className="st-toast-title">{e.title}</div>
151
+ <div className="st-toast-txt">{e.summary}</div>
152
+ <div className="st-toast-actions">
153
+ <button type="button" className="btn btn--primary btn--sm" onClick={wn.openPanel}>
154
+ {total > 1 ? `See all (${total})` : 'Details'}
155
+ </button>
156
+ <button type="button" className="btn btn--ghost btn--sm" onClick={wn.dismissToast}>
157
+ Dismiss
158
+ </button>
159
+ </div>
160
+ </div>
161
+ );
162
+ }
163
+
164
+ export function WhatsNewPanel({ wn, onStartTour }) {
165
+ const { panelOpen, closePanel } = wn;
166
+ useEffect(() => {
167
+ if (!panelOpen) return;
168
+ function onKey(e) {
169
+ if (e.key === 'Escape') closePanel();
170
+ }
171
+ window.addEventListener('keydown', onKey);
172
+ return () => window.removeEventListener('keydown', onKey);
173
+ }, [panelOpen, closePanel]);
174
+
175
+ if (!panelOpen) return null;
176
+ const unseenIds = new Set(wn.unseen.map((e) => e.id));
177
+ return (
178
+ <div
179
+ className="help-modal-backdrop"
180
+ role="presentation"
181
+ onMouseDown={(e) => {
182
+ if (e.target === e.currentTarget) closePanel();
183
+ }}
184
+ >
185
+ <div
186
+ className="mdcc-wn-panel"
187
+ role="dialog"
188
+ aria-modal="true"
189
+ aria-labelledby="mdcc-wn-title"
190
+ >
191
+ <header className="help-modal-hd">
192
+ <span className="title" id="mdcc-wn-title">
193
+ ✦ What’s new in maude
194
+ </span>
195
+ <button
196
+ type="button"
197
+ className="help-modal-close"
198
+ aria-label="Close (Esc)"
199
+ onClick={closePanel}
200
+ >
201
+ ×
202
+ </button>
203
+ </header>
204
+ <div className="mdcc-wn-panel__body">
205
+ {wn.entries.length === 0 ? (
206
+ <p className="mdcc-wn-empty">Nothing new yet.</p>
207
+ ) : (
208
+ <ul className="mdcc-wn-list">
209
+ {wn.entries.map((e) => (
210
+ <li
211
+ key={e.id}
212
+ className={'mdcc-wn-item' + (unseenIds.has(e.id) ? ' is-unseen' : '')}
213
+ >
214
+ <div className="mdcc-wn-item__hd">
215
+ <span className={'mdcc-wn-kind mdcc-wn-kind--' + e.kind}>{e.kind}</span>
216
+ <span className="mdcc-wn-item__title">{e.title}</span>
217
+ <span className="mdcc-wn-item__ver">{e.version ? `v${e.version}` : 'next'}</span>
218
+ </div>
219
+ <p className="mdcc-wn-item__summary">{e.summary}</p>
220
+ {isSafeHref(e.learnMore) && (
221
+ <a
222
+ className="mdcc-wn-item__more"
223
+ href={e.learnMore}
224
+ target="_blank"
225
+ rel="noreferrer"
226
+ >
227
+ Learn more →
228
+ </a>
229
+ )}
230
+ {onStartTour && Array.isArray(e.tour) && e.tour.length > 0 && (
231
+ <button
232
+ type="button"
233
+ className="mdcc-wn-item__tour"
234
+ onClick={() => {
235
+ closePanel();
236
+ onStartTour(e.tour);
237
+ }}
238
+ >
239
+ ▶ Take tour
240
+ </button>
241
+ )}
242
+ </li>
243
+ ))}
244
+ </ul>
245
+ )}
246
+ </div>
247
+ </div>
248
+ </div>
249
+ );
250
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @file commands/annotation-strokes-command.ts — undo entry for annotation
3
3
  * stroke add / erase / batch-translate.
4
- * @scope plugins/design/dev-server/commands/annotation-strokes-command.ts
4
+ * @scope apps/studio/commands/annotation-strokes-command.ts
5
5
  * @purpose Records a `Stroke[]` pair (before/after) and routes both
6
6
  * directions through a single `putFn`. The server endpoint is
7
7
  * `PUT /_api/annotations` which replaces the entire SVG; we
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @file commands/equal-spacing-command.ts — undo label helpers for
3
3
  * equal-spacing / align gestures.
4
- * @scope plugins/design/dev-server/commands/equal-spacing-command.ts
4
+ * @scope apps/studio/commands/equal-spacing-command.ts
5
5
  * @purpose Distribute + align both call `dragBus.commitPositions(moved)`,
6
6
  * which wraps the move in a generic MoveArtboardsCommand. Per
7
7
  * DDR-050 the underlying command type is shared (the inverse
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file commands/move-artboards-command.ts — undo entry for artboard moves
3
- * @scope plugins/design/dev-server/commands/move-artboards-command.ts
3
+ * @scope apps/studio/commands/move-artboards-command.ts
4
4
  * @purpose Reversible record of an artboard-layout PATCH. Pairs the full
5
5
  * `before` and `after` layout snapshots with an injected
6
6
  * `patchFn` (in production, `applyArtboardLayout`; in tests, a
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file comments-overlay.tsx — FigJam-style in-place comments overlay
3
- * @scope plugins/design/dev-server/comments-overlay.tsx
3
+ * @scope apps/studio/comments-overlay.tsx
4
4
  * @purpose Renders DS-styled comment pins (Phase 6 Task 2), the in-place
5
5
  * composer bubble (Task 3), and the thread popover (Task 4)
6
6
  * inside the canvas iframe. Sibling to `annotations-layer` —
@@ -81,6 +81,9 @@ export interface OverlayComment {
81
81
  id: string;
82
82
  file: string;
83
83
  selector: string;
84
+ /** Occurrence index among `querySelectorAll(selector)` — disambiguates a
85
+ * component repeated within one artboard. Absent (old comments) → first. */
86
+ index?: number;
84
87
  bounds: OverlayBounds | null;
85
88
  text: string;
86
89
  status: 'open' | 'resolved';
@@ -138,7 +141,10 @@ function deriveFile(): string | null {
138
141
  // halo chrome at z-index 5) instead of being portaled into `.dc-world` where
139
142
  // it would lose the stacking battle.
140
143
 
141
- function screenRectFor(selector: string): {
144
+ function screenRectFor(
145
+ selector: string,
146
+ index?: number
147
+ ): {
142
148
  x: number;
143
149
  y: number;
144
150
  w: number;
@@ -147,7 +153,11 @@ function screenRectFor(selector: string): {
147
153
  if (!selector) return null;
148
154
  let el: HTMLElement | null = null;
149
155
  try {
150
- el = document.querySelector(selector) as HTMLElement | null;
156
+ // index disambiguates a component repeated within one artboard (querySelector
157
+ // alone would always grab the first match). Absent/0 → first.
158
+ const all = document.querySelectorAll(selector);
159
+ const i = index && index > 0 && index < all.length ? index : 0;
160
+ el = (all[i] ?? all[0] ?? null) as HTMLElement | null;
151
161
  } catch {
152
162
  return null;
153
163
  }
@@ -316,6 +326,7 @@ export function CommentsOverlay(): React.ReactNode {
316
326
  const payload = {
317
327
  file: sel.file,
318
328
  selector: sel.selector,
329
+ index: sel.index,
319
330
  dom_path: sel.dom_path,
320
331
  tag: sel.tag,
321
332
  classes: sel.classes,
@@ -453,6 +464,7 @@ export function CommentsOverlay(): React.ReactNode {
453
464
  return (
454
465
  <CommentThread
455
466
  comment={focused}
467
+ sequence={indexById.get(focused.id) ?? 0}
456
468
  onClose={() => {
457
469
  setFocusedId(null);
458
470
  // Drop the canvas halo when the thread closes — symmetric with
@@ -745,7 +757,7 @@ function CommentPin({
745
757
  // Live screen-coord lookup mirrors SelectionHalos in canvas-shell.tsx.
746
758
  // Falls back to stored bounds (a screen-coord capture at create time)
747
759
  // when the target element is gone — better than vanishing entirely.
748
- let pos = screenRectFor(comment.selector);
760
+ let pos = screenRectFor(comment.selector, comment.index);
749
761
  if (!pos && comment.bounds) {
750
762
  pos = {
751
763
  x: comment.bounds.x,
@@ -925,12 +937,14 @@ function CommentComposer({
925
937
 
926
938
  function CommentThread({
927
939
  comment,
940
+ sequence,
928
941
  onClose,
929
942
  onPatch,
930
943
  onDelete,
931
944
  onReply,
932
945
  }: {
933
946
  comment: OverlayComment;
947
+ sequence: number;
934
948
  onClose: () => void;
935
949
  onPatch: (patch: Record<string, unknown>) => void;
936
950
  onDelete: () => void;
@@ -1027,6 +1041,11 @@ function CommentThread({
1027
1041
  >
1028
1042
  <div className="cm-thread__head" id={headId}>
1029
1043
  <div className="cm-thread__head-row">
1044
+ {/* Plan C P18 — pin/sequence badge in the popover header (parity with
1045
+ `.design/ui/Studio.tsx` thread popover). */}
1046
+ <span className="cm-thread__seq" aria-hidden="true">
1047
+ {sequence}
1048
+ </span>
1030
1049
  <span className="cm-thread__author">{comment.author?.trim() || 'unknown'}</span>
1031
1050
  <span className="cm-thread__time">{formatRelativeTime(comment.created)}</span>
1032
1051
  <button
@@ -1159,7 +1178,7 @@ function computeThreadAnchor(comment: OverlayComment): { x: number; y: number }
1159
1178
  // Resolve target's live screen rect; popover drops below the pin with small
1160
1179
  // breathing room. Stored bounds (capture-time screen coords) are the
1161
1180
  // last-resort fallback for orphaned pins.
1162
- const rect = comment.selector ? screenRectFor(comment.selector) : null;
1181
+ const rect = comment.selector ? screenRectFor(comment.selector, comment.index) : null;
1163
1182
  if (rect) {
1164
1183
  // Pin sits at (rect.right - 12, rect.top - 12). Place popover at the same
1165
1184
  // x for visual continuity, 16px below the top so it clears the pin.
@@ -1184,7 +1203,7 @@ function computeAnchor(state: ComposerState): { x: number; y: number } {
1184
1203
  return { x: state.clientX, y: state.clientY + 8 };
1185
1204
  }
1186
1205
  if (state.selection.selector) {
1187
- const rect = screenRectFor(state.selection.selector);
1206
+ const rect = screenRectFor(state.selection.selector, state.selection.index);
1188
1207
  if (rect) {
1189
1208
  return { x: rect.x, y: rect.y + rect.h + 8 };
1190
1209
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file context-menu.tsx — Phase 4.1 right-click context menu
3
- * @scope plugins/design/dev-server/context-menu.tsx
3
+ * @scope apps/studio/context-menu.tsx
4
4
  * @purpose Floating, context-aware right-click menu. The router calls
5
5
  * `openContextMenu({ clientX, clientY, target })`; the menu
6
6
  * resolves the section list via the ContextRegistry and renders