@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
@@ -3,6 +3,7 @@
3
3
  // Targets the 8 quick-win fixes recorded in DDR-054 §2 against the findings
4
4
  // in `.ai/logs/security-reviews/phase-9-task-4-bidi-fs-sync-{defender,attacker}.md`.
5
5
 
6
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
6
7
  import {
7
8
  chmodSync,
8
9
  existsSync,
@@ -15,20 +16,18 @@ import {
15
16
  } from 'node:fs';
16
17
  import { tmpdir } from 'node:os';
17
18
  import { join } from 'node:path';
18
-
19
- import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
20
19
  import * as Y from 'yjs';
21
20
 
22
21
  import { createCanvasSyncAgent } from '../sync/agent.ts';
23
22
  import { atomicWrite } from '../sync/atomic-write.ts';
24
23
  import {
25
- MAX_ANNOTATIONS_BYTES,
26
- MAX_COMMENTS_BYTES,
27
- MAX_HTML_BYTES,
28
24
  applyAnnotationsToDoc,
29
25
  applyCommentsToDoc,
30
26
  applyHtmlToDoc,
31
27
  htmlFromDoc,
28
+ MAX_ANNOTATIONS_BYTES,
29
+ MAX_COMMENTS_BYTES,
30
+ MAX_HTML_BYTES,
32
31
  } from '../sync/codec.ts';
33
32
  import { createEchoGuard, hashBytes } from '../sync/echo-guard.ts';
34
33
  import { createFsReader } from '../sync/fs-mirror.ts';
@@ -1,11 +1,10 @@
1
1
  // hubs-config reader tests — Phase 9 Task 4.
2
2
 
3
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
3
4
  import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
4
5
  import { tmpdir } from 'node:os';
5
6
  import { join } from 'node:path';
6
7
 
7
- import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
8
-
9
8
  import { getHubToken, hubsConfigPath, loadHubsConfig, normalizeUrl } from '../sync/hubs-config.ts';
10
9
 
11
10
  let dir: string;
@@ -5,6 +5,7 @@
5
5
  // honors linkedHub config, discovers canvases, wires up agents, and
6
6
  // dispatches fs events through the bus correctly.
7
7
 
8
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
8
9
  import {
9
10
  chmodSync,
10
11
  existsSync,
@@ -16,8 +17,6 @@ import {
16
17
  } from 'node:fs';
17
18
  import { tmpdir } from 'node:os';
18
19
  import { join } from 'node:path';
19
-
20
- import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
21
20
  import { Awareness } from 'y-protocols/awareness';
22
21
  import * as Y from 'yjs';
23
22
 
@@ -29,10 +28,10 @@ import { createBus } from '../context.ts';
29
28
  import { createConnectionMonitor } from '../sync/connection-state.ts';
30
29
  import {
31
30
  type AwarenessRegistry,
32
- type SyncProvider,
33
31
  buildNoSyncablePayload,
34
32
  createSyncRuntime,
35
33
  discoverCanvases,
34
+ type SyncProvider,
36
35
  scanCanvases,
37
36
  toWsUrl,
38
37
  } from '../sync/index.ts';
@@ -108,11 +107,7 @@ function makeCtx(linkedHub?: DevServerConfig['linkedHub'], canvasOrigin?: string
108
107
  }
109
108
 
110
109
  function inMemoryProviderFactory(): {
111
- factory: (args: {
112
- url: string;
113
- token: string;
114
- documentName: string;
115
- }) => SyncProvider;
110
+ factory: (args: { url: string; token: string; documentName: string }) => SyncProvider;
116
111
  peerOf: (slug: string) => Y.Doc;
117
112
  } {
118
113
  // Map of slug -> { local, peer } Y.Docs cross-linked via applyUpdate.
@@ -3,7 +3,7 @@
3
3
  import { describe, expect, test } from 'bun:test';
4
4
 
5
5
  import type { SyncStatusSnapshot } from '../sync/connection-state.ts';
6
- import { type SyncStatusPayload, createSyncStatusStore } from '../sync/status.ts';
6
+ import { createSyncStatusStore, type SyncStatusPayload } from '../sync/status.ts';
7
7
 
8
8
  function snap(partial: Partial<SyncStatusSnapshot> = {}): SyncStatusSnapshot {
9
9
  return {
@@ -3,12 +3,11 @@
3
3
  // for the syncable set, cleared when empty, stale entries dropped on re-write,
4
4
  // and user-authored `.claudeignore` content outside the block preserved.
5
5
 
6
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
6
7
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
7
8
  import { tmpdir } from 'node:os';
8
9
  import { join } from 'node:path';
9
10
 
10
- import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
11
-
12
11
  import type { Context } from '../context.ts';
13
12
  import type { CanvasDescriptor } from '../sync/index.ts';
14
13
  import { clearUntrustedMarkers, writeUntrustedMarkers } from '../sync/untrusted.ts';
@@ -0,0 +1,56 @@
1
+ // tour-overlay — Phase 3. Render contract for the hand-rolled tour engine.
2
+ //
3
+ // renderToStaticMarkup doesn't run effects, so the rect stays null on the
4
+ // server render → the overlay falls back to a centered card + scrim. We assert
5
+ // the a11y dialog contract + step/nav wiring; keyboard/focus-trap behavior is a
6
+ // live-only concern (verified via agent-browser).
7
+
8
+ import { describe, expect, test } from 'bun:test';
9
+ import { renderToStaticMarkup } from 'react-dom/server';
10
+ import { TourOverlay } from '../client/tour/overlay.jsx';
11
+
12
+ const STEP = { target: '.x', title: 'Welcome', body: 'Body copy here' };
13
+
14
+ describe('TourOverlay', () => {
15
+ test('renders an accessible dialog with the first step', () => {
16
+ const html = renderToStaticMarkup(
17
+ <TourOverlay open steps={[STEP]} onClose={() => {}} onComplete={() => {}} />
18
+ );
19
+ expect(html).toContain('mdcc-tour');
20
+ expect(html).toContain('role="dialog"');
21
+ expect(html).toContain('aria-modal="true"');
22
+ expect(html).toContain('Welcome');
23
+ expect(html).toContain('Body copy here');
24
+ expect(html).toContain('1 / 1');
25
+ expect(html).toContain('mdcc-tour__scrim'); // no rect on the server → scrim, not spotlight
26
+ });
27
+
28
+ test('single step shows Done (not Next) and no Back', () => {
29
+ const html = renderToStaticMarkup(
30
+ <TourOverlay open steps={[STEP]} onClose={() => {}} onComplete={() => {}} />
31
+ );
32
+ expect(html).toContain('Done');
33
+ expect(html).not.toContain('mdcc-tour__back');
34
+ });
35
+
36
+ test('multi-step first page shows Next, the counter, and no Back yet', () => {
37
+ const steps = [STEP, { ...STEP, title: 'Two' }, { ...STEP, title: 'Three' }];
38
+ const html = renderToStaticMarkup(
39
+ <TourOverlay open steps={steps} onClose={() => {}} onComplete={() => {}} />
40
+ );
41
+ expect(html).toContain('1 / 3');
42
+ expect(html).toContain('Next');
43
+ expect(html).not.toContain('mdcc-tour__back'); // i === 0
44
+ });
45
+
46
+ test('closed or empty renders nothing', () => {
47
+ expect(
48
+ renderToStaticMarkup(
49
+ <TourOverlay open={false} steps={[STEP]} onClose={() => {}} onComplete={() => {}} />
50
+ )
51
+ ).toBe('');
52
+ expect(
53
+ renderToStaticMarkup(<TourOverlay open steps={[]} onClose={() => {}} onComplete={() => {}} />)
54
+ ).toBe('');
55
+ });
56
+ });
@@ -64,7 +64,10 @@ describe('use-agent-presence / provider gating', () => {
64
64
  function Wrap({
65
65
  initialAgent,
66
66
  children,
67
- }: { initialAgent: AgentPresence | null; children: ReactNode }) {
67
+ }: {
68
+ initialAgent: AgentPresence | null;
69
+ children: ReactNode;
70
+ }) {
68
71
  return <AgentPresenceProvider initialAgent={initialAgent}>{children}</AgentPresenceProvider>;
69
72
  }
70
73
 
@@ -10,6 +10,8 @@ import { describe, expect, test } from 'bun:test';
10
10
  import type {
11
11
  ArrowStroke,
12
12
  EllipseStroke,
13
+ ImageStroke,
14
+ LinkStroke,
13
15
  PenStroke,
14
16
  PolygonStroke,
15
17
  RectStroke,
@@ -123,6 +125,47 @@ describe('resizeStroke / sticky — always 1:1 square', () => {
123
125
  });
124
126
  });
125
127
 
128
+ describe('resizeStroke / image — Phase 23 (aspect-lock by default, Shift frees)', () => {
129
+ const image: ImageStroke = {
130
+ id: 'im1',
131
+ tool: 'image',
132
+ x: 0,
133
+ y: 0,
134
+ w: 100,
135
+ h: 50, // 2:1 intrinsic aspect
136
+ href: 'assets/abcd1234.png',
137
+ };
138
+ test('no modifiers keeps the intrinsic aspect (the inverse of shapes)', () => {
139
+ // se → 120,80 would be 2.4:1 free, but the lock keeps 2:1 (dominant axis).
140
+ expect(resizeStroke(image, 'se', 120, 80)).toEqual({ x: 0, y: 0, w: 160, h: 80 });
141
+ });
142
+ test('Shift held inverts to a free (unconstrained) resize', () => {
143
+ expect(resizeStroke(image, 'se', 120, 80, { shift: true, alt: false })).toEqual({
144
+ x: 0,
145
+ y: 0,
146
+ w: 120,
147
+ h: 80,
148
+ });
149
+ });
150
+ });
151
+
152
+ describe('resizeStroke / link — Phase 23 (free-resizes like a rect)', () => {
153
+ const link: LinkStroke = {
154
+ id: 'lk1',
155
+ tool: 'link',
156
+ x: 0,
157
+ y: 0,
158
+ w: 200,
159
+ h: 80,
160
+ url: 'https://example.com',
161
+ title: 'Example',
162
+ domain: 'example.com',
163
+ };
164
+ test('no modifiers free-resizes (no forced aspect)', () => {
165
+ expect(resizeStroke(link, 'se', 300, 120)).toEqual({ x: 0, y: 0, w: 300, h: 120 });
166
+ });
167
+ });
168
+
126
169
  describe('resizeStroke / ellipse — Alt keeps the center fixed', () => {
127
170
  const ell: EllipseStroke = {
128
171
  id: 'e1',
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { describe, expect, test } from 'bun:test';
4
4
 
5
- import { type Rect, type SnapOptions, computeSnap } from '../use-snap-guides.tsx';
5
+ import { computeSnap, type Rect, type SnapOptions } from '../use-snap-guides.tsx';
6
6
 
7
7
  const DEFAULTS: SnapOptions = { gridSize: 40, tolerance: 8, disabled: false };
8
8
 
@@ -12,12 +12,13 @@ import {
12
12
  } from '../use-tool-mode.tsx';
13
13
 
14
14
  describe('use-tool-mode / static', () => {
15
- test('DEFAULT_TOOLS exposes V/H/C + draw set B/R/N/A/T/E (Phase 24 single Shape tool)', () => {
15
+ test('DEFAULT_TOOLS exposes V/H/C + draw set B/I/R/N/A/T/E (single Shape tool + highlighter)', () => {
16
16
  expect(DEFAULT_TOOLS.map((t) => t.id)).toEqual([
17
17
  'move',
18
18
  'hand',
19
19
  'comment',
20
20
  'pen',
21
+ 'highlighter',
21
22
  'shape',
22
23
  'sticky',
23
24
  'arrow',
@@ -29,6 +30,7 @@ describe('use-tool-mode / static', () => {
29
30
  'H',
30
31
  'C',
31
32
  'B',
33
+ 'I',
32
34
  'R',
33
35
  'N',
34
36
  'A',
@@ -50,6 +52,7 @@ describe('use-tool-mode / static', () => {
50
52
  'hand',
51
53
  'comment',
52
54
  'pen',
55
+ 'highlighter',
53
56
  'shape',
54
57
  'sticky',
55
58
  'arrow',
@@ -0,0 +1,101 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { mkdtempSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { bestSeenVersion, computeUnseen, parseVer, verGt } from '../client/whats-new-seen.js';
6
+ import { loadWhatsNew, resolveMaudeVersion } from '../whats-new.ts';
7
+
8
+ describe('whats-new feed loader', () => {
9
+ test('loads the committed feed with a version + non-empty entries', () => {
10
+ const feed = loadWhatsNew({ fresh: true });
11
+ expect(typeof feed.version).toBe('string');
12
+ expect(Array.isArray(feed.entries)).toBe(true);
13
+ expect(feed.entries.length).toBeGreaterThan(0);
14
+ });
15
+
16
+ test('every entry has the required shape', () => {
17
+ for (const e of loadWhatsNew({ fresh: true }).entries) {
18
+ expect(typeof e.id).toBe('string');
19
+ expect(e.id).toMatch(/^[a-z0-9][a-z0-9-]*$/);
20
+ expect(['feature', 'improvement', 'usage', 'fix']).toContain(e.kind);
21
+ expect(e.title.length).toBeGreaterThan(0);
22
+ expect(e.summary.length).toBeGreaterThan(0);
23
+ }
24
+ });
25
+
26
+ test('resolveMaudeVersion reads the design plugin manifest', () => {
27
+ expect(resolveMaudeVersion()).toMatch(/^\d+\.\d+\.\d+/);
28
+ });
29
+
30
+ test('a missing feed dir yields empty entries and never throws', () => {
31
+ const feed = loadWhatsNew({ root: join(import.meta.dir, '__does-not-exist__'), fresh: true });
32
+ expect(feed.entries).toEqual([]);
33
+ });
34
+
35
+ // Security regression (ethical-hacker creativity finding): the feed MUST
36
+ // resolve from the maude package root, NEVER a served project's tree. A
37
+ // hostile served project dropping its own whats-new.json must not inject
38
+ // entries into the privileged main-origin UI. Pin the boundary so a future
39
+ // "project-overridable feed" change can't silently erode it.
40
+ test('default resolution ignores a feed planted in an arbitrary (served-project) dir', () => {
41
+ const hostile = mkdtempSync(join(tmpdir(), 'mdcc-served-'));
42
+ writeFileSync(
43
+ join(hostile, 'whats-new.json'),
44
+ JSON.stringify({
45
+ entries: [{ id: 'evil', kind: 'feature', title: 'pwn', summary: 'x' }],
46
+ })
47
+ );
48
+ const feed = loadWhatsNew({ fresh: true }); // no root → package root only
49
+ expect(feed.entries.some((e) => e.id === 'evil')).toBe(false);
50
+ expect(feed.entries.length).toBeGreaterThan(0); // still reads the real package feed
51
+ });
52
+
53
+ // Defense-in-depth contract: a rendered learnMore is always an http(s) URL.
54
+ test('every committed entry learnMore is http(s) (no javascript:/data: scheme)', () => {
55
+ for (const e of loadWhatsNew({ fresh: true }).entries) {
56
+ if (e.learnMore != null) expect(e.learnMore).toMatch(/^https?:\/\//);
57
+ }
58
+ });
59
+ });
60
+
61
+ describe('seen logic (browser-safe, shared with the client)', () => {
62
+ test('parseVer extracts a semver triple', () => {
63
+ expect(parseVer('0.28.1')).toEqual([0, 28, 1]);
64
+ expect(parseVer('1.2.3-beta.4')).toEqual([1, 2, 3]);
65
+ expect(parseVer('dev')).toBeNull();
66
+ });
67
+
68
+ test('verGt compares semver, treating unparseable seen as oldest', () => {
69
+ expect(verGt('0.29.0', '0.28.1')).toBe(true);
70
+ expect(verGt('0.28.1', '0.29.0')).toBe(false);
71
+ expect(verGt('1.0.0', '0.99.99')).toBe(true);
72
+ expect(verGt('0.28.1', '0.28.1')).toBe(false);
73
+ expect(verGt('0.28.1', 'dev')).toBe(true);
74
+ expect(verGt('dev', '0.28.1')).toBe(false);
75
+ });
76
+
77
+ test('pending (null version) entries are always unseen', () => {
78
+ const u = computeUnseen(
79
+ [{ id: 'a', version: null, kind: 'feature', title: 't', summary: 's' }],
80
+ '9.9.9'
81
+ );
82
+ expect(u.map((e) => e.id)).toEqual(['a']);
83
+ });
84
+
85
+ test('computeUnseen returns only entries newer than the seen version', () => {
86
+ const entries = [
87
+ { id: 'old', version: '0.27.0', kind: 'feature', title: 't', summary: 's' },
88
+ { id: 'new', version: '0.29.0', kind: 'feature', title: 't', summary: 's' },
89
+ ];
90
+ expect(computeUnseen(entries, '0.28.0').map((e) => e.id)).toEqual(['new']);
91
+ expect(computeUnseen(entries, '0.29.0')).toEqual([]);
92
+ });
93
+
94
+ test('bestSeenVersion picks the highest of feed + entry versions', () => {
95
+ expect(bestSeenVersion([{ version: '0.29.0' }, { version: '0.27.0' }], '0.28.1')).toBe(
96
+ '0.29.0'
97
+ );
98
+ expect(bestSeenVersion([{ version: '0.20.0' }], '0.28.1')).toBe('0.28.1');
99
+ expect(bestSeenVersion([{ version: null }], '0.28.1')).toBe('0.28.1');
100
+ });
101
+ });
@@ -0,0 +1,9 @@
1
+ // Ambient declarations for Bun text imports — `import x from './f.ext' with { type: 'text' }`.
2
+ // The dev-server runs under Bun (which honors the import attribute) and ships as
3
+ // a `bun --compile` binary (which inlines the text at build time). tsc only needs
4
+ // to know the module resolves to a string default export.
5
+
6
+ declare module '*.tsx.template' {
7
+ const content: string;
8
+ export default content;
9
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file tool-palette.tsx — Phase 5.1 centered icon toolbar
3
- * @scope plugins/design/dev-server/tool-palette.tsx
3
+ * @scope apps/studio/tool-palette.tsx
4
4
  * @purpose Bottom-center floating chrome — navigate (V/H/C), draw
5
5
  * (B/R/O/A/E), and zoom in one shell. Adopts the dev-server
6
6
  * menubar's visual language (8 px radius, soft shadow, hairline
@@ -20,7 +20,6 @@ import {
20
20
  SHAPE_KIND_ICONS,
21
21
  TOOL_ICONS,
22
22
  } from './canvas-icons.tsx';
23
- import { useViewportControllerContext } from './canvas-lib.tsx';
24
23
  import { useAnnotationsVisibility } from './use-annotations-visibility.tsx';
25
24
  import { type ShapeKind, useToolMode } from './use-tool-mode.tsx';
26
25
 
@@ -229,7 +228,7 @@ function ensurePaletteStyles(): void {
229
228
  const NAV_TOOLS = ['move', 'hand', 'comment'] as const;
230
229
  // Phase 24 — the two rect/ellipse buttons collapse into one Shape tool (with a
231
230
  // kind popover); sticky/arrow/text/eraser keep their order.
232
- const DRAW_TOOLS = ['pen', 'shape', 'sticky', 'arrow', 'text', 'eraser'] as const;
231
+ const DRAW_TOOLS = ['pen', 'highlighter', 'shape', 'sticky', 'arrow', 'text', 'eraser'] as const;
233
232
 
234
233
  // Phase 24 — the Shape tool's primitive picker (popover order matches FigJam).
235
234
  const SHAPE_KINDS: ReadonlyArray<{ kind: ShapeKind; label: string }> = [
@@ -247,25 +246,21 @@ const SHAPE_KINDS: ReadonlyArray<{ kind: ShapeKind; label: string }> = [
247
246
  export function ToolPalette() {
248
247
  ensurePaletteStyles();
249
248
  const { tool, setTool, tools, sticky, toggleSticky, shapeKind, setShapeKind } = useToolMode();
250
- const controller = useViewportControllerContext();
251
249
  const visibilityCtx = useAnnotationsVisibility();
252
250
  const [mounted, setMounted] = useState(false);
253
- const [zoomOpen, setZoomOpen] = useState(false);
254
251
  const [shapeOpen, setShapeOpen] = useState(false);
255
252
  const containerRef = useRef<HTMLDivElement | null>(null);
256
253
 
257
254
  useEffect(() => setMounted(true), []);
258
255
  useEffect(() => {
259
- if (!zoomOpen && !shapeOpen) return;
256
+ if (!shapeOpen) return;
260
257
  const onDown = (e: PointerEvent) => {
261
258
  if (!containerRef.current?.contains(e.target as Node)) {
262
- setZoomOpen(false);
263
259
  setShapeOpen(false);
264
260
  }
265
261
  };
266
262
  const onKey = (e: KeyboardEvent) => {
267
263
  if (e.key === 'Escape') {
268
- setZoomOpen(false);
269
264
  setShapeOpen(false);
270
265
  }
271
266
  };
@@ -275,7 +270,7 @@ export function ToolPalette() {
275
270
  document.removeEventListener('pointerdown', onDown, true);
276
271
  document.removeEventListener('keydown', onKey, true);
277
272
  };
278
- }, [zoomOpen, shapeOpen]);
273
+ }, [shapeOpen]);
279
274
  // Close the shape popover whenever the tool changes away from Shape.
280
275
  useEffect(() => {
281
276
  if (tool !== 'shape') setShapeOpen(false);
@@ -286,7 +281,6 @@ export function ToolPalette() {
286
281
  const byId = new Map(tools.map((t) => [t.id, t]));
287
282
  const navList = NAV_TOOLS.map((id) => byId.get(id)).filter(Boolean);
288
283
  const drawList = DRAW_TOOLS.map((id) => byId.get(id)).filter(Boolean);
289
- const pct = controller ? Math.round(controller.viewport.zoom * 100) : 100;
290
284
  const annotationsHidden = visibilityCtx ? !visibilityCtx.visible : false;
291
285
 
292
286
  const renderToolButton = (id: string, label: string, shortcut: string) => {
@@ -417,66 +411,7 @@ export function ToolPalette() {
417
411
  >
418
412
  <IconPresentation />
419
413
  </button>
420
- <button
421
- type="button"
422
- className="dc-tp-zoom"
423
- aria-label={`Zoom ${pct}%, open zoom menu`}
424
- aria-expanded={zoomOpen}
425
- title={`Zoom ${pct}%`}
426
- onClick={() => setZoomOpen((o) => !o)}
427
- >
428
- <span>{pct}%</span>
429
- <IconChevronDown size={12} />
430
- </button>
431
414
  </div>
432
- {zoomOpen && controller ? (
433
- <div className="dc-tp-popover" role="menu" aria-label="Zoom">
434
- <button
435
- type="button"
436
- role="menuitem"
437
- onClick={() => {
438
- controller.zoomIn();
439
- setZoomOpen(false);
440
- }}
441
- >
442
- <span>Zoom In</span>
443
- <span className="dc-tp-kbd">⌘ +</span>
444
- </button>
445
- <button
446
- type="button"
447
- role="menuitem"
448
- onClick={() => {
449
- controller.zoomOut();
450
- setZoomOpen(false);
451
- }}
452
- >
453
- <span>Zoom Out</span>
454
- <span className="dc-tp-kbd">⌘ −</span>
455
- </button>
456
- <button
457
- type="button"
458
- role="menuitem"
459
- onClick={() => {
460
- controller.fit();
461
- setZoomOpen(false);
462
- }}
463
- >
464
- <span>Fit to Screen</span>
465
- <span className="dc-tp-kbd">⌘ 0</span>
466
- </button>
467
- <button
468
- type="button"
469
- role="menuitem"
470
- onClick={() => {
471
- controller.reset();
472
- setZoomOpen(false);
473
- }}
474
- >
475
- <span>Actual Size 100%</span>
476
- <span className="dc-tp-kbd">⌘ 1</span>
477
- </button>
478
- </div>
479
- ) : null}
480
415
  </div>
481
416
  );
482
417
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file undo-hud.tsx — top-right toast announcing the last undo edit
3
- * @scope plugins/design/dev-server/undo-hud.tsx
3
+ * @scope apps/studio/undo-hud.tsx
4
4
  * @purpose Subscribes to `useUndoStack().lastLabel` + `.lastTick` and
5
5
  * surfaces a 1.2 s aria-live toast so the user (and a screen
6
6
  * reader) knows which edit just got pushed / undone / redone.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file undo-stack.ts — per-canvas in-memory command stack
3
- * @scope plugins/design/dev-server/undo-stack.ts
3
+ * @scope apps/studio/undo-stack.ts
4
4
  * @purpose Pure state + reducer for canvas Cmd+Z / Cmd+Shift+Z. No React,
5
5
  * no DOM, no fetch — `EditCommand.do()` / `.undo()` are caller-
6
6
  * supplied side-effects so this file stays trivially testable
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file use-agent-presence.tsx — Phase 13.2 / DDR-078 agent presence.
3
- * @scope plugins/design/dev-server/use-agent-presence.tsx
3
+ * @scope apps/studio/use-agent-presence.tsx
4
4
  * @purpose Turn the Phase 8 `ai-activity` heartbeat into a *presence* peer:
5
5
  * when an agent (`/design:edit` / `/design:new`) is editing THIS
6
6
  * canvas, surface it like another connected human — an avatar in the
@@ -21,7 +21,7 @@
21
21
  * same cross-bundle rule as the activity context (DDR-077 lesson).
22
22
  */
23
23
 
24
- import { type ReactNode, createContext, useContext, useEffect, useMemo, useState } from 'react';
24
+ import { createContext, type ReactNode, useContext, useEffect, useMemo, useState } from 'react';
25
25
 
26
26
  import { colorForName } from './use-collab.tsx';
27
27
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file use-annotation-resize.tsx — Task 23 (Wave 2, G4)
3
- * @scope plugins/design/dev-server/use-annotation-resize.tsx
3
+ * @scope apps/studio/use-annotation-resize.tsx
4
4
  * @purpose Screen-space corner / endpoint handles for the selected
5
5
  * annotation. Per-tool resize math with FigJam resize modifiers:
6
6
  * Shift = lock aspect ratio (45° angle snap for arrows), Alt =
@@ -22,6 +22,8 @@ import { type ReactNode, useCallback, useEffect, useMemo, useRef } from 'react';
22
22
  import {
23
23
  type ArrowStroke,
24
24
  type EllipseStroke,
25
+ type ImageStroke,
26
+ type LinkStroke,
25
27
  type PenStroke,
26
28
  type PolygonStroke,
27
29
  type RectStroke,
@@ -72,14 +74,24 @@ type Corner = 'nw' | 'ne' | 'sw' | 'se' | 'ep1' | 'ep2';
72
74
  /** Stroke types that expose resize handles. Text inherits its anchor bbox. */
73
75
  function isResizable(
74
76
  s: Stroke
75
- ): s is RectStroke | EllipseStroke | PolygonStroke | ArrowStroke | PenStroke | StickyStroke {
77
+ ): s is
78
+ | RectStroke
79
+ | EllipseStroke
80
+ | PolygonStroke
81
+ | ArrowStroke
82
+ | PenStroke
83
+ | StickyStroke
84
+ | ImageStroke
85
+ | LinkStroke {
76
86
  return (
77
87
  s.tool === 'rect' ||
78
88
  s.tool === 'ellipse' ||
79
89
  s.tool === 'polygon' ||
80
90
  s.tool === 'arrow' ||
81
91
  s.tool === 'pen' ||
82
- s.tool === 'sticky'
92
+ s.tool === 'sticky' ||
93
+ s.tool === 'image' ||
94
+ s.tool === 'link'
83
95
  );
84
96
  }
85
97
 
@@ -170,9 +182,15 @@ export function resizeStroke(
170
182
  wy: number,
171
183
  mods: ResizeMods = NO_MODS
172
184
  ): Partial<Stroke> | null {
173
- if (start.tool === 'rect' || start.tool === 'sticky' || start.tool === 'polygon') {
174
- // Rect / polygon / sticky all resize via their shared x / y / w / h bbox.
175
- // Text re-wraps inside the foreignObject automatically. Sticky stays 1:1.
185
+ if (
186
+ start.tool === 'rect' ||
187
+ start.tool === 'sticky' ||
188
+ start.tool === 'polygon' ||
189
+ start.tool === 'link'
190
+ ) {
191
+ // Rect / polygon / sticky / link all resize via their shared x / y / w / h
192
+ // bbox. Text re-wraps inside the foreignObject automatically. Sticky stays
193
+ // 1:1; the link card free-resizes (Shift still locks its current ratio).
176
194
  const box = bboxResize(
177
195
  { x: start.x, y: start.y, w: start.w, h: start.h },
178
196
  corner,
@@ -181,7 +199,22 @@ export function resizeStroke(
181
199
  mods,
182
200
  start.tool === 'sticky'
183
201
  );
184
- return box as Partial<RectStroke | StickyStroke | PolygonStroke>;
202
+ return box as Partial<RectStroke | StickyStroke | PolygonStroke | LinkStroke>;
203
+ }
204
+ if (start.tool === 'image') {
205
+ // Phase 23 — images aspect-LOCK by default and free-resize with Shift held
206
+ // (the inverse of the shape tools — Figma/FigJam image behaviour). Invert
207
+ // the Shift flag into bboxResize, which keeps the START ratio when shift is
208
+ // set; the start ratio IS the image's intrinsic aspect.
209
+ const box = bboxResize(
210
+ { x: start.x, y: start.y, w: start.w, h: start.h },
211
+ corner,
212
+ wx,
213
+ wy,
214
+ { shift: !mods.shift, alt: mods.alt },
215
+ false
216
+ );
217
+ return box as Partial<ImageStroke>;
185
218
  }
186
219
  if (start.tool === 'ellipse') {
187
220
  // Treat the four corners as the bbox of the ellipse, then derive cx/cy/rx/ry.
@@ -297,11 +330,7 @@ function handlePositions(s: Stroke): Array<{ corner: Corner; x: number; y: numbe
297
330
  ];
298
331
  }
299
332
 
300
- export function AnnotationResizeOverlay({
301
- store,
302
- }: {
303
- store: StrokesStoreValue | null;
304
- }): ReactNode {
333
+ export function AnnotationResizeOverlay({ store }: { store: StrokesStoreValue | null }): ReactNode {
305
334
  ensureResizeStyles();
306
335
  const annotSel = useAnnotationSelection();
307
336
  const controller = useViewportControllerContext();