@1agh/maude 0.28.1 → 0.30.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.
- package/README.md +3 -3
- package/{plugins/design/dev-server → apps/studio}/ai-banner.tsx +1 -2
- package/apps/studio/annotations-align.ts +149 -0
- package/apps/studio/annotations-bindings.ts +197 -0
- package/{plugins/design/dev-server → apps/studio}/annotations-context-toolbar.tsx +681 -151
- package/apps/studio/annotations-groups.ts +270 -0
- package/apps/studio/annotations-layer.tsx +4537 -0
- package/apps/studio/annotations-model.ts +2077 -0
- package/apps/studio/annotations-snap.ts +125 -0
- package/{plugins/design/dev-server → apps/studio}/api.ts +616 -70
- package/{plugins/design/dev-server → apps/studio}/artboard-activity-overlay.tsx +1 -1
- package/{plugins/design/dev-server → apps/studio}/artboard-marquee.tsx +1 -1
- package/apps/studio/bin/_motion-sample-playwright.mjs +129 -0
- package/{plugins/design/dev-server → apps/studio}/bin/_png-playwright.mjs +1 -1
- package/apps/studio/bin/annotate.mjs +732 -0
- package/apps/studio/bin/annotate.sh +17 -0
- package/{plugins/design/dev-server → apps/studio}/bin/canvas-edit.sh +1 -1
- package/{plugins/design/dev-server → apps/studio}/bin/draw-proof.sh +37 -2
- package/{plugins/design/dev-server → apps/studio}/bin/preflight.sh +1 -1
- package/apps/studio/bin/read-annotations.mjs +573 -0
- package/apps/studio/bin/read-annotations.sh +11 -0
- package/{plugins/design/dev-server → apps/studio}/bin/runtime-health.sh +1 -1
- package/{plugins/design/dev-server → apps/studio}/bin/screenshot.sh +62 -2
- package/{plugins/design/dev-server → apps/studio}/bin/server-up.sh +9 -3
- package/apps/studio/bin/to-lottie-verify.html +68 -0
- package/apps/studio/bin/to-lottie.sh +128 -0
- package/{plugins/design/dev-server → apps/studio}/build.ts +6 -4
- package/{plugins/design/dev-server → apps/studio}/canvas-arrowheads.ts +79 -10
- package/{plugins/design/dev-server → apps/studio}/canvas-comment-mount.tsx +1 -1
- package/apps/studio/canvas-create.ts +104 -0
- package/{plugins/design/dev-server → apps/studio}/canvas-cursors.ts +48 -12
- package/{plugins/design/dev-server → apps/studio}/canvas-edit.ts +257 -7
- package/{plugins/design/dev-server → apps/studio}/canvas-icons.tsx +165 -1
- package/{plugins/design/dev-server → apps/studio}/canvas-lib-inline.ts +1 -1
- package/{plugins/design/dev-server → apps/studio}/canvas-lib-resolver.ts +1 -1
- package/{plugins/design/dev-server → apps/studio}/canvas-lib.tsx +31 -12
- package/{plugins/design/dev-server → apps/studio}/canvas-shell.tsx +360 -48
- package/apps/studio/client/app.jsx +6742 -0
- package/apps/studio/client/canvas-url.js +81 -0
- package/{plugins/design/dev-server → apps/studio}/client/comments-overlay.css +41 -27
- package/apps/studio/client/index.html +16 -0
- package/apps/studio/client/styles/1-tokens-maude.css +296 -0
- package/apps/studio/client/styles/3-shell-maude.css +1161 -0
- package/{plugins/design/dev-server → apps/studio}/client/styles/3-shell.css +129 -0
- package/apps/studio/client/styles/4-components-maude.css +74 -0
- package/{plugins/design/dev-server → apps/studio}/client/styles/4-components.css +396 -34
- package/apps/studio/client/styles/5-maude-overrides.css +137 -0
- package/{plugins/design/dev-server → apps/studio}/client/styles/_index.css +9 -0
- package/{plugins/design/dev-server → apps/studio}/client/styles.css +1 -1
- package/apps/studio/client/tour/overlay.jsx +279 -0
- package/apps/studio/client/tour/usage-tour.js +48 -0
- package/apps/studio/client/whats-new-seen.js +52 -0
- package/apps/studio/client/whats-new.jsx +250 -0
- package/{plugins/design/dev-server → apps/studio}/commands/annotation-strokes-command.ts +2 -2
- package/{plugins/design/dev-server → apps/studio}/commands/equal-spacing-command.ts +1 -1
- package/{plugins/design/dev-server → apps/studio}/commands/move-artboards-command.ts +2 -2
- package/{plugins/design/dev-server → apps/studio}/comments-overlay.tsx +32 -11
- package/{plugins/design/dev-server → apps/studio}/context-menu.tsx +5 -4
- package/{plugins/design/dev-server → apps/studio}/contextual-toolbar.tsx +18 -7
- package/{plugins/design/dev-server → apps/studio}/cursors-overlay.tsx +21 -21
- package/apps/studio/dist/client.bundle.js +25545 -0
- package/apps/studio/dist/comment-mount.js +2046 -0
- package/apps/studio/dist/styles.css +8018 -0
- package/apps/studio/dom-selection.ts +342 -0
- package/apps/studio/draw/animate.ts +333 -0
- package/{plugins/design/dev-server → apps/studio}/draw/brush.ts +1 -1
- package/{plugins/design/dev-server → apps/studio}/draw/composition.ts +1 -1
- package/{plugins/design/dev-server → apps/studio}/draw/geometry.ts +1 -1
- package/{plugins/design/dev-server → apps/studio}/draw/index.ts +4 -1
- package/{plugins/design/dev-server → apps/studio}/draw/layout.ts +1 -1
- package/apps/studio/draw/morph.ts +204 -0
- package/{plugins/design/dev-server → apps/studio}/draw/optimize.ts +1 -1
- package/{plugins/design/dev-server → apps/studio}/draw/palette.ts +1 -1
- package/{plugins/design/dev-server → apps/studio}/draw/primitives.ts +1 -1
- package/apps/studio/draw/serialize-animate.ts +324 -0
- package/{plugins/design/dev-server → apps/studio}/draw/serialize.ts +9 -3
- package/apps/studio/draw/test/animate.test.ts +172 -0
- package/apps/studio/draw/test/morph.test.ts +107 -0
- package/apps/studio/draw/test/serialize-animate.test.ts +142 -0
- package/{plugins/design/dev-server → apps/studio}/equal-spacing-detector.ts +1 -1
- package/{plugins/design/dev-server → apps/studio}/equal-spacing-handles.tsx +7 -29
- package/{plugins/design/dev-server → apps/studio}/export-dialog.tsx +16 -4
- package/{plugins/design/dev-server → apps/studio}/history.ts +20 -3
- package/{plugins/design/dev-server → apps/studio}/http.ts +226 -6
- package/{plugins/design/dev-server → apps/studio}/input-router.tsx +15 -1
- package/{plugins/design/dev-server → apps/studio}/inspect.ts +50 -4
- package/{plugins/design/dev-server → apps/studio}/marquee-overlay.tsx +5 -2
- package/{plugins/design/dev-server → apps/studio}/participants-chrome.tsx +2 -8
- package/{plugins/design/dev-server → apps/studio}/paths.ts +15 -11
- package/{plugins/design/dev-server → apps/studio}/server.ts +23 -2
- package/{plugins/design/dev-server → apps/studio}/sync/agent.ts +231 -57
- package/{plugins/design/dev-server → apps/studio}/sync/codec.ts +45 -0
- package/apps/studio/sync/cold-start.ts +158 -0
- package/{plugins/design/dev-server → apps/studio}/sync/connection-state.ts +58 -2
- package/{plugins/design/dev-server → apps/studio}/sync/index.ts +563 -221
- package/apps/studio/sync/journal.ts +190 -0
- package/apps/studio/sync/migrate-seed.ts +321 -0
- package/{plugins/design/dev-server → apps/studio}/sync/projection.ts +22 -3
- package/{plugins/design/dev-server → apps/studio}/sync/status.ts +15 -4
- package/{plugins/design/dev-server → apps/studio}/test/activity.test.ts +1 -7
- package/apps/studio/test/annotate-write.test.ts +184 -0
- package/apps/studio/test/annotations-align.test.ts +88 -0
- package/apps/studio/test/annotations-bindings.test.ts +124 -0
- package/apps/studio/test/annotations-groups.test.ts +231 -0
- package/{plugins/design/dev-server → apps/studio}/test/annotations-layer.test.ts +63 -0
- package/{plugins/design/dev-server → apps/studio}/test/annotations-roundtrip.test.ts +97 -0
- package/apps/studio/test/annotations-snap.test.ts +79 -0
- package/apps/studio/test/annotations-text-format.test.ts +396 -0
- package/apps/studio/test/asset-api.test.ts +149 -0
- package/apps/studio/test/canvas-create-api.test.ts +361 -0
- package/{plugins/design/dev-server → apps/studio}/test/canvas-cursors.test.ts +2 -0
- package/apps/studio/test/canvas-edit.test.ts +319 -0
- package/{plugins/design/dev-server → apps/studio}/test/canvas-lib-resolver.test.ts +1 -1
- package/apps/studio/test/canvas-media-drop.test.ts +158 -0
- package/{plugins/design/dev-server → apps/studio}/test/canvas-origin-gate.test.ts +25 -1
- package/apps/studio/test/canvas-url.test.ts +99 -0
- package/{plugins/design/dev-server → apps/studio}/test/collab-bridge.test.ts +0 -2
- package/{plugins/design/dev-server → apps/studio}/test/collab-room.test.ts +2 -7
- package/{plugins/design/dev-server → apps/studio}/test/csp-canvas-shell.test.ts +5 -0
- package/apps/studio/test/csrf-write-guard.test.ts +52 -0
- package/apps/studio/test/figjam-v3-model.test.ts +342 -0
- package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +1 -0
- package/apps/studio/test/index-data-canvas-ds.test.ts +97 -0
- package/apps/studio/test/motion-sample.test.ts +35 -0
- package/{plugins/design/dev-server → apps/studio}/test/perf-harness.ts +1 -1
- package/{plugins/design/dev-server → apps/studio}/test/phase-3.6-smoke.test.ts +3 -1
- package/apps/studio/test/read-annotations.test.ts +409 -0
- package/{plugins/design/dev-server → apps/studio}/test/sanitize-annotation-svg.test.ts +108 -2
- package/{plugins/design/dev-server → apps/studio}/test/shared-doc-convergence.test.ts +1 -1
- package/apps/studio/test/shared-doc-migrate.test.ts +369 -0
- package/apps/studio/test/sync-agent.test.ts +575 -0
- package/{plugins/design/dev-server → apps/studio}/test/sync-codec.test.ts +65 -0
- package/apps/studio/test/sync-cold-start.test.ts +244 -0
- package/{plugins/design/dev-server → apps/studio}/test/sync-connection-state.test.ts +68 -0
- package/{plugins/design/dev-server → apps/studio}/test/sync-hardening.test.ts +0 -8
- package/{plugins/design/dev-server → apps/studio}/test/sync-hubs-config.test.ts +0 -1
- package/apps/studio/test/sync-incident-replay.test.ts +211 -0
- package/apps/studio/test/sync-journal.test.ts +176 -0
- package/{plugins/design/dev-server → apps/studio}/test/sync-runtime.test.ts +360 -5
- package/{plugins/design/dev-server → apps/studio}/test/sync-status.test.ts +66 -0
- package/apps/studio/test/tour-overlay.test.tsx +56 -0
- package/apps/studio/test/use-annotation-resize.test.ts +402 -0
- package/{plugins/design/dev-server → apps/studio}/test/use-artboard-drag.test.ts +0 -1
- package/{plugins/design/dev-server → apps/studio}/test/use-collab.test.ts +0 -0
- package/{plugins/design/dev-server → apps/studio}/test/use-tool-mode.test.tsx +8 -2
- package/apps/studio/test/whats-new.test.ts +101 -0
- package/apps/studio/text-imports.d.ts +9 -0
- package/{plugins/design/dev-server → apps/studio}/tool-palette.tsx +15 -70
- package/{plugins/design/dev-server → apps/studio}/undo-hud.tsx +1 -2
- package/{plugins/design/dev-server → apps/studio}/undo-stack.ts +1 -1
- package/{plugins/design/dev-server → apps/studio}/use-agent-presence.tsx +5 -3
- package/apps/studio/use-annotation-resize.tsx +912 -0
- package/{plugins/design/dev-server → apps/studio}/use-annotation-selection.tsx +17 -5
- package/{plugins/design/dev-server → apps/studio}/use-annotations-visibility.tsx +1 -1
- package/{plugins/design/dev-server → apps/studio}/use-artboard-drag.tsx +1 -1
- package/{plugins/design/dev-server → apps/studio}/use-canvas-activity.tsx +3 -3
- package/apps/studio/use-canvas-media-drop.tsx +343 -0
- package/{plugins/design/dev-server → apps/studio}/use-collab.tsx +21 -16
- package/{plugins/design/dev-server → apps/studio}/use-cursor-modifiers.tsx +1 -1
- package/{plugins/design/dev-server → apps/studio}/use-keyboard-discipline.tsx +5 -2
- package/{plugins/design/dev-server → apps/studio}/use-selection-set.tsx +18 -1
- package/{plugins/design/dev-server → apps/studio}/use-snap-guides.tsx +1 -1
- package/{plugins/design/dev-server → apps/studio}/use-tool-mode.tsx +8 -2
- package/{plugins/design/dev-server → apps/studio}/use-undo-stack.tsx +1 -1
- package/apps/studio/whats-new.json +264 -0
- package/apps/studio/whats-new.schema.json +86 -0
- package/apps/studio/whats-new.ts +87 -0
- package/cli/bin/maude.mjs +10 -0
- package/cli/commands/design-link.test.mjs +84 -0
- package/cli/commands/design.mjs +16 -7
- package/cli/commands/design.test.mjs +40 -0
- package/cli/commands/help.mjs +8 -2
- package/cli/commands/hub.mjs +3 -3
- package/cli/commands/hub.test.mjs +1 -1
- package/cli/commands/scenario-report.mjs +2 -9
- package/cli/lib/design-link.mjs +51 -1
- package/cli/lib/flow-design-integration.test.mjs +1 -1
- package/cli/lib/gitignore-block.mjs +1 -0
- package/cli/lib/plugin-cli-reachability.test.mjs +8 -7
- package/cli/lib/preflight.mjs +1 -1
- package/package.json +15 -15
- package/plugins/design/templates/_shell.html +28 -4
- package/plugins/design/templates/brief-board.tsx.template +61 -0
- package/plugins/design/templates/canvas.tsx.template +1 -1
- package/plugins/design/templates/design-system-inspiration/core/config.json.tpl +1 -1
- package/plugins/design/templates/design-system-inspiration/core/preview/_motion-readme.md.tpl +1 -1
- package/plugins/flow/.claude-plugin/config.schema.json +16 -0
- package/plugins/design/dev-server/annotations-layer.tsx +0 -3402
- package/plugins/design/dev-server/client/app.jsx +0 -2985
- package/plugins/design/dev-server/client/index.html +0 -15
- package/plugins/design/dev-server/dist/client.bundle.js +0 -20692
- package/plugins/design/dev-server/dist/comment-mount.js +0 -1924
- package/plugins/design/dev-server/dist/styles.css +0 -2710
- package/plugins/design/dev-server/dom-selection.ts +0 -156
- package/plugins/design/dev-server/sync/migrate-seed.ts +0 -163
- package/plugins/design/dev-server/test/canvas-edit.test.ts +0 -139
- package/plugins/design/dev-server/test/shared-doc-migrate.test.ts +0 -159
- package/plugins/design/dev-server/test/sync-agent.test.ts +0 -305
- package/plugins/design/dev-server/test/use-annotation-resize.test.ts +0 -200
- package/plugins/design/dev-server/use-annotation-resize.tsx +0 -449
- /package/{plugins/design/dev-server → apps/studio}/activity.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/_enumerate-artboards-playwright.mjs +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/_html-playwright.mjs +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/_pdf-playwright.mjs +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/_pptx-playwright.mjs +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/_pw-launch.mjs +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/_screenshot-playwright.mjs +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/_svg-optimize.mjs +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/_svg-playwright.mjs +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/asset-sweep.sh +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/bootstrap-check.sh +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/check-runtime-bundles.sh +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/draw-build.sh +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/handoff.sh +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/prep.sh +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/scenario-report.mjs +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/slug.sh +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/smoke.sh +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/svg-optimize.sh +0 -0
- /package/{plugins/design/dev-server → apps/studio}/bin/visual-sanity.sh +0 -0
- /package/{plugins/design/dev-server → apps/studio}/boot-self-heal.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/canvas-build.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/canvas-header.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/canvas-meta.schema.json +0 -0
- /package/{plugins/design/dev-server → apps/studio}/canvas-pipeline.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/client/hmr.mjs +0 -0
- /package/{plugins/design/dev-server → apps/studio}/client/iframe-lazy.mjs +0 -0
- /package/{plugins/design/dev-server → apps/studio}/client/styles/0-reset.css +0 -0
- /package/{plugins/design/dev-server → apps/studio}/client/styles/1-tokens.css +0 -0
- /package/{plugins/design/dev-server → apps/studio}/client/styles/2-layout.css +0 -0
- /package/{plugins/design/dev-server → apps/studio}/client/styles/5-utilities.css +0 -0
- /package/{plugins/design/dev-server → apps/studio}/collab/ai-activity.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/collab/awareness-bridge.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/collab/git-lifecycle.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/collab/index.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/collab/persistence.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/collab/protocol.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/collab/registry.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/collab/room.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/config.schema.json +0 -0
- /package/{plugins/design/dev-server → apps/studio}/context.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/dist/runtime/.min-sizes.json +0 -0
- /package/{plugins/design/dev-server → apps/studio}/dist/runtime/lib0_decoding.js +0 -0
- /package/{plugins/design/dev-server → apps/studio}/dist/runtime/lib0_encoding.js +0 -0
- /package/{plugins/design/dev-server → apps/studio}/dist/runtime/motion.js +0 -0
- /package/{plugins/design/dev-server → apps/studio}/dist/runtime/motion_react.js +0 -0
- /package/{plugins/design/dev-server → apps/studio}/dist/runtime/pixi-js.js +0 -0
- /package/{plugins/design/dev-server → apps/studio}/dist/runtime/react-dom.js +0 -0
- /package/{plugins/design/dev-server → apps/studio}/dist/runtime/react-dom_client.js +0 -0
- /package/{plugins/design/dev-server → apps/studio}/dist/runtime/react.js +0 -0
- /package/{plugins/design/dev-server → apps/studio}/dist/runtime/react_jsx-dev-runtime.js +0 -0
- /package/{plugins/design/dev-server → apps/studio}/dist/runtime/react_jsx-runtime.js +0 -0
- /package/{plugins/design/dev-server → apps/studio}/dist/runtime/y-protocols_awareness.js +0 -0
- /package/{plugins/design/dev-server → apps/studio}/dist/runtime/y-protocols_sync.js +0 -0
- /package/{plugins/design/dev-server → apps/studio}/dist/runtime/yjs.js +0 -0
- /package/{plugins/design/dev-server → apps/studio}/draw/test/brush.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/draw/test/composition.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/draw/test/geometry.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/draw/test/gradient.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/draw/test/layout.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/draw/test/optimize.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/draw/test/palette.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/draw/test/primitives.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/draw/test/serialize.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/examples/README.md +0 -0
- /package/{plugins/design/dev-server → apps/studio}/examples/perf-100-artboards.tsx +0 -0
- /package/{plugins/design/dev-server → apps/studio}/exporters/_browser-bundles.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/exporters/canva-handoff-prompt.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/exporters/canva.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/exporters/html.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/exporters/index.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/exporters/pdf.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/exporters/png.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/exporters/pptx.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/exporters/scope.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/exporters/svg.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/exporters/zip.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/fs-watch.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/handoff.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/hmr-broadcast.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/locator.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/mem.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/runtime-bundle.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/server.mjs +0 -0
- /package/{plugins/design/dev-server → apps/studio}/sync/atomic-write.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/sync/echo-guard.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/sync/fs-mirror.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/sync/hubs-config.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/sync/materialize.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/sync/origins.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/sync/untrusted.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/_helpers.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/active-state.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/ai-activity.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/annotation-strokes-command.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/annotations-api.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/annotations-draw-modifiers.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/artboard-activity-overlay.test.tsx +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/binary-smoke.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/boot-self-heal.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/bundle-smoke.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/canvas-build.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/canvas-header.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/canvas-hmr-runtime.test.tsx +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/canvas-lib-inline.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/canvas-lib-motion.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/canvas-meta-api.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/canvas-pipeline.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/canvas-route.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/collab-annotations-bridge.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/collab-awareness-bridge.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/collab-loopback.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/collab-protocol.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/collab-reseed-guard.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/collab-stress.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/comment-mount.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/comments-api.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/compile-entry.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/context-resolve-tokens.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/equal-spacing-detector.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/exporters/canva.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/exporters/endpoint.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/exporters/history.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/exporters/html.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/exporters/pdf.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/exporters/png.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/exporters/pptx-deck.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/exporters/pptx.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/exporters/pw-launch.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/exporters/scope.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/exporters/svg.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/exporters/zip.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/fixtures/phase-20-annotations.svg +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/fixtures/phase-21-annotations.svg +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/fs-watch.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/git-lifecycle.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/handoff-static-frames.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/handoff.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/history-rollback.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/hmr-broadcast.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/hmr-classify.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/input-router.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/locator.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/marquee-overlay.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/move-artboards-command.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/participants-chrome.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/paths.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/runtime-bundle-error-mapping.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/runtime-bundle.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/server-lifecycle.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/shared-doc-foundation.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/shared-doc-projection.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/snap-distance-pill.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/sync-atomic-write.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/sync-echo-guard.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/sync-fs-mirror.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/sync-meta-codec.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/sync-untrusted.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/system-endpoint.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/tool-palette.test.tsx +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/undo-stack.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/use-agent-presence.test.tsx +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/use-annotation-selection.test.tsx +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/use-canvas-activity.test.tsx +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/use-cursor-modifiers.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/use-keyboard-discipline.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/use-selection-set.test.tsx +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/use-snap-guides.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/use-undo-stack.test.tsx +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/ws-handshake.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/tsconfig.json +0 -0
- /package/{plugins/design/dev-server → apps/studio}/ws.ts +0 -0
|
@@ -39,6 +39,11 @@ describe('cspForCanvasShell — A6 hardening', () => {
|
|
|
39
39
|
expect(csp).toContain("frame-ancestors 'self' http://localhost:4399");
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
test('frame-ancestors carries every advertised loopback spelling (localhost + 127.0.0.1)', () => {
|
|
43
|
+
const csp = cspForCanvasShell(SHELL, 'http://localhost:4399 http://127.0.0.1:4399');
|
|
44
|
+
expect(csp).toContain("frame-ancestors 'self' http://localhost:4399 http://127.0.0.1:4399");
|
|
45
|
+
});
|
|
46
|
+
|
|
42
47
|
test('frame-ancestors is OMITTED when the main origin is unknown (would block the legit embed)', () => {
|
|
43
48
|
const csp = cspForCanvasShell(SHELL);
|
|
44
49
|
expect(csp).not.toContain('frame-ancestors');
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// CSRF Origin guard for the main-origin source-write routes (edit-css /
|
|
2
|
+
// edit-text / edit-attr). DDR-105. The DDR-054 origin-split already blocks the
|
|
3
|
+
// untrusted canvas *iframe* (canvas-origin-gate.test.ts proves that). This guard
|
|
4
|
+
// covers the OTHER untrusted origin: a malicious top-level page in another tab
|
|
5
|
+
// forging a `text/plain` CORS simple-request POST to localhost. The browser
|
|
6
|
+
// stamps such a request with an unspoofable cross-origin `Origin` header, so the
|
|
7
|
+
// guard must reject it — while still letting the legit same-origin shell write
|
|
8
|
+
// and not breaking non-browser clients (which send no Origin header).
|
|
9
|
+
//
|
|
10
|
+
// Unit-level on purpose: `sameOriginWrite` is a pure function of the Request, so
|
|
11
|
+
// we exercise the decision directly instead of booting a subprocess server.
|
|
12
|
+
// (A booted-server integration test would add a 124th server-booting file to the
|
|
13
|
+
// suite, which perturbs bun's worker scheduling and surfaces a PRE-EXISTING
|
|
14
|
+
// cross-worker port-collision flake in canvas-meta-api.test.ts — a test-harness
|
|
15
|
+
// race unrelated to this guard. See the Phase 12.2 validate notes.)
|
|
16
|
+
|
|
17
|
+
import { describe, expect, test } from 'bun:test';
|
|
18
|
+
|
|
19
|
+
import { sameOriginWrite } from '../http.ts';
|
|
20
|
+
|
|
21
|
+
const SELF = 'http://localhost:4399';
|
|
22
|
+
const post = (origin?: string): Request =>
|
|
23
|
+
new Request(`${SELF}/_api/edit-css`, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: { 'content-type': 'text/plain', ...(origin ? { origin } : {}) },
|
|
26
|
+
body: '{}',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('CSRF Origin guard — sameOriginWrite (DDR-105)', () => {
|
|
30
|
+
test('rejects a forged cross-origin Origin', () => {
|
|
31
|
+
expect(sameOriginWrite(post('http://evil.example'))).toBe(false);
|
|
32
|
+
// A look-alike host (substring / suffix tricks) must not slip through.
|
|
33
|
+
expect(sameOriginWrite(post('http://localhost.evil.example'))).toBe(false);
|
|
34
|
+
expect(sameOriginWrite(post('http://localhost:4399.evil.example'))).toBe(false);
|
|
35
|
+
// Right host, wrong port is still cross-origin.
|
|
36
|
+
expect(sameOriginWrite(post('http://localhost:4400'))).toBe(false);
|
|
37
|
+
// Right host:port, wrong scheme is still cross-origin.
|
|
38
|
+
expect(sameOriginWrite(post('https://localhost:4399'))).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('allows the legit same-origin shell Origin', () => {
|
|
42
|
+
expect(sameOriginWrite(post(SELF))).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('allows a request with no Origin header (curl / programmatic / bun:test)', () => {
|
|
46
|
+
expect(sameOriginWrite(post())).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('rejects an unparseable Origin rather than letting it through', () => {
|
|
50
|
+
expect(sameOriginWrite(post('not a url'))).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
// FigJam v3 — model schema extension: data-group-ids / data-author /
|
|
2
|
+
// data-start-bind / data-end-bind. Three load-bearing assertions:
|
|
3
|
+
// 1. the new fixture round-trips BYTE-IDENTICAL (parse → re-serialize),
|
|
4
|
+
// 2. legacy strokes (no new fields) serialize WITHOUT any new attribute
|
|
5
|
+
// (the Phase-24 canary holds — covered again here from the model side),
|
|
6
|
+
// 3. the sanitizer passes the new data-* attrs through (it denylists only
|
|
7
|
+
// on*/style/href) and poisoned bind values are rejected on parse.
|
|
8
|
+
|
|
9
|
+
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
|
10
|
+
import { GlobalRegistrator } from '@happy-dom/global-registrator';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
type AnchorHost,
|
|
14
|
+
type ArrowStroke,
|
|
15
|
+
convertShapeKind,
|
|
16
|
+
type PolygonStroke,
|
|
17
|
+
type RectStroke,
|
|
18
|
+
type StickyStroke,
|
|
19
|
+
type Stroke,
|
|
20
|
+
shapeKindOf,
|
|
21
|
+
strokeBBox,
|
|
22
|
+
strokeHitTest,
|
|
23
|
+
strokesToSvg,
|
|
24
|
+
svgToStrokes,
|
|
25
|
+
type TextStroke,
|
|
26
|
+
} from '../annotations-model.ts';
|
|
27
|
+
import { sanitizeAnnotationSvg } from '../api.ts';
|
|
28
|
+
|
|
29
|
+
beforeAll(() => {
|
|
30
|
+
GlobalRegistrator.register();
|
|
31
|
+
});
|
|
32
|
+
afterAll(async () => {
|
|
33
|
+
await GlobalRegistrator.unregister();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const FIXTURE_URL = new URL('./fixtures/figjam-v3-groups-bindings.svg', import.meta.url);
|
|
37
|
+
|
|
38
|
+
describe('figjam-v3 fixture canary', () => {
|
|
39
|
+
test('round-trips byte-identical (groups + nested groups + binds + author)', async () => {
|
|
40
|
+
const fixture = await Bun.file(FIXTURE_URL).text();
|
|
41
|
+
expect(strokesToSvg(svgToStrokes(fixture))).toBe(fixture);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('round-trips byte-identical THROUGH the sanitizer', async () => {
|
|
45
|
+
const fixture = await Bun.file(FIXTURE_URL).text();
|
|
46
|
+
expect(sanitizeAnnotationSvg(fixture)).toBe(fixture);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('parse recovers the new fields exactly', async () => {
|
|
50
|
+
const strokes = svgToStrokes(await Bun.file(FIXTURE_URL).text());
|
|
51
|
+
const rect = strokes.find((s) => s.id === 's_aaa1') as RectStroke;
|
|
52
|
+
expect(rect.groupIds).toEqual(['g_team1']);
|
|
53
|
+
const sticky = strokes.find((s) => s.id === 's_aaa2') as StickyStroke;
|
|
54
|
+
expect(sticky.groupIds).toEqual(['g_inner', 'g_team1']); // deepest→shallowest order kept
|
|
55
|
+
const arrow = strokes.find((s) => s.id === 's_aaa3') as ArrowStroke;
|
|
56
|
+
expect(arrow.startBind).toEqual({ hostId: 's_aaa1', nx: 1, ny: 0.5 }); // auto
|
|
57
|
+
expect(arrow.endBind).toEqual({ hostId: 's_aaa2', nx: 0, ny: 0.5, pinned: true });
|
|
58
|
+
const note = strokes.find((s) => s.id === 's_aaa4') as TextStroke;
|
|
59
|
+
expect(note.author).toBe('ai');
|
|
60
|
+
// The ellipse carries rotation but none of the other new fields.
|
|
61
|
+
const ell = strokes.find((s) => s.id === 's_aaa5') as Stroke;
|
|
62
|
+
expect(ell.rotation).toBe(15);
|
|
63
|
+
expect('groupIds' in ell).toBe(false);
|
|
64
|
+
expect('author' in ell).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('serialize-only-non-default invariant for the v3 fields', () => {
|
|
69
|
+
test('a legacy stroke set emits NO v3 attribute', () => {
|
|
70
|
+
const strokes: Stroke[] = [
|
|
71
|
+
{ id: 's_1', tool: 'rect', color: '#1f1f1f', width: 3, x: 0, y: 0, w: 10, h: 10 },
|
|
72
|
+
{ id: 's_2', tool: 'arrow', color: '#1f1f1f', width: 3, x1: 0, y1: 0, x2: 9, y2: 0 },
|
|
73
|
+
];
|
|
74
|
+
const svg = strokesToSvg(strokes);
|
|
75
|
+
expect(svg).not.toContain('data-group-ids');
|
|
76
|
+
expect(svg).not.toContain('data-author');
|
|
77
|
+
expect(svg).not.toContain('data-start-bind');
|
|
78
|
+
expect(svg).not.toContain('data-end-bind');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('empty groupIds array serializes as no attribute', () => {
|
|
82
|
+
const svg = strokesToSvg([
|
|
83
|
+
{
|
|
84
|
+
id: 's_1',
|
|
85
|
+
tool: 'rect',
|
|
86
|
+
color: '#1f1f1f',
|
|
87
|
+
width: 3,
|
|
88
|
+
x: 0,
|
|
89
|
+
y: 0,
|
|
90
|
+
w: 10,
|
|
91
|
+
h: 10,
|
|
92
|
+
groupIds: [],
|
|
93
|
+
},
|
|
94
|
+
]);
|
|
95
|
+
expect(svg).not.toContain('data-group-ids');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('poisoned input hardening', () => {
|
|
100
|
+
test('malformed / out-of-range bind values are rejected on parse', () => {
|
|
101
|
+
const bad = [
|
|
102
|
+
'data-start-bind="onlyhost"',
|
|
103
|
+
'data-start-bind="h 2 0.5"', // nx out of [0..1]
|
|
104
|
+
'data-start-bind="h NaN 0.5"',
|
|
105
|
+
'data-start-bind="h 0.5 -1"',
|
|
106
|
+
];
|
|
107
|
+
for (const attr of bad) {
|
|
108
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" data-mdcc-annotations="1"><g data-id="s_x" data-tool="arrow" stroke="#1f1f1f" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" vector-effect="non-scaling-stroke" fill="none" ${attr}><line x1="0" y1="0" x2="9" y2="0"/><polyline points="1,1 9,0 1,-1" fill="#1f1f1f"/></g></svg>`;
|
|
109
|
+
const arrow = svgToStrokes(svg).find((s) => s.tool === 'arrow') as ArrowStroke;
|
|
110
|
+
expect(arrow).toBeDefined();
|
|
111
|
+
expect(arrow.startBind).toBeUndefined();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('data-author only honours the literal "ai"', () => {
|
|
116
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" data-mdcc-annotations="1"><rect data-id="s_x" data-tool="rect" stroke="#1f1f1f" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" vector-effect="non-scaling-stroke" fill="none" x="0" y="0" width="10" height="10" data-author="evil"/></svg>`;
|
|
117
|
+
const rect = svgToStrokes(svg)[0] as RectStroke;
|
|
118
|
+
expect('author' in rect).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('sanitizer still strips handlers glued next to the new attrs', () => {
|
|
122
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg"><rect data-tool="rect" data-group-ids="g_1" onload="alert(1)" x="0" y="0" width="10" height="10"/></svg>`;
|
|
123
|
+
const clean = sanitizeAnnotationSvg(svg);
|
|
124
|
+
expect(clean).toContain('data-group-ids="g_1"');
|
|
125
|
+
expect(clean).not.toContain('onload');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('rotation (FigJam v3)', () => {
|
|
130
|
+
test('serializes data-rot + presentational transform only when non-zero; round-trips', () => {
|
|
131
|
+
const r: Stroke = {
|
|
132
|
+
id: 's_r',
|
|
133
|
+
tool: 'rect',
|
|
134
|
+
color: '#1f1f1f',
|
|
135
|
+
width: 3,
|
|
136
|
+
x: 0,
|
|
137
|
+
y: 0,
|
|
138
|
+
w: 100,
|
|
139
|
+
h: 50,
|
|
140
|
+
rotation: 30,
|
|
141
|
+
};
|
|
142
|
+
const svg = strokesToSvg([r]);
|
|
143
|
+
expect(svg).toContain('data-rot="30"');
|
|
144
|
+
expect(svg).toContain('transform="rotate(30 50 25)"');
|
|
145
|
+
const back = svgToStrokes(svg);
|
|
146
|
+
expect((back[0] as RectStroke).rotation).toBe(30);
|
|
147
|
+
expect(strokesToSvg(back)).toBe(svg);
|
|
148
|
+
expect(strokesToSvg([{ ...r, rotation: 0 }])).not.toContain('data-rot');
|
|
149
|
+
expect(sanitizeAnnotationSvg(svg)).toBe(svg);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('hit-testing happens in the rotated frame', () => {
|
|
153
|
+
// A wide flat bar rotated 90° stands upright: a probe above the original
|
|
154
|
+
// top edge now hits; the original east tip is empty space.
|
|
155
|
+
const bar: Stroke = {
|
|
156
|
+
id: 's_b',
|
|
157
|
+
tool: 'rect',
|
|
158
|
+
color: '#000',
|
|
159
|
+
width: 2,
|
|
160
|
+
x: 0,
|
|
161
|
+
y: 0,
|
|
162
|
+
w: 100,
|
|
163
|
+
h: 10,
|
|
164
|
+
fill: '#fff',
|
|
165
|
+
rotation: 90,
|
|
166
|
+
};
|
|
167
|
+
expect(strokeHitTest(bar, 50, -40, 2)).toBe(true);
|
|
168
|
+
expect(strokeHitTest(bar, 95, 5, 2)).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('pen/arrow/anchored text ignore rotation (no attr emitted)', () => {
|
|
172
|
+
const pen: Stroke = {
|
|
173
|
+
id: 's_p',
|
|
174
|
+
tool: 'pen',
|
|
175
|
+
color: '#000',
|
|
176
|
+
width: 3,
|
|
177
|
+
points: [
|
|
178
|
+
[0, 0],
|
|
179
|
+
[10, 10],
|
|
180
|
+
],
|
|
181
|
+
rotation: 45,
|
|
182
|
+
};
|
|
183
|
+
expect(strokesToSvg([pen])).not.toContain('data-rot');
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('polygon anchored text (Wave G)', () => {
|
|
188
|
+
const diamond: PolygonStroke = {
|
|
189
|
+
id: 'poly1',
|
|
190
|
+
tool: 'polygon',
|
|
191
|
+
shape: 'diamond',
|
|
192
|
+
color: '#1a1a1a',
|
|
193
|
+
width: 2,
|
|
194
|
+
x: 10,
|
|
195
|
+
y: 20,
|
|
196
|
+
w: 120,
|
|
197
|
+
h: 80,
|
|
198
|
+
};
|
|
199
|
+
const label: TextStroke = {
|
|
200
|
+
id: 'txt1',
|
|
201
|
+
tool: 'text',
|
|
202
|
+
color: '#1a1a1a',
|
|
203
|
+
fontSize: 16,
|
|
204
|
+
text: 'decision',
|
|
205
|
+
anchorId: 'poly1',
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
test('anchored text inherits a polygon host bbox', () => {
|
|
209
|
+
const anchors = new Map<string, AnchorHost>([[diamond.id, diamond]]);
|
|
210
|
+
expect(strokeBBox(label, anchors)).toEqual({ x: 10, y: 20, w: 120, h: 80 });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('serialize → parse round-trips the anchor binding', () => {
|
|
214
|
+
const svg = strokesToSvg([diamond, label]);
|
|
215
|
+
expect(svg).toContain('data-anchor-id="poly1"');
|
|
216
|
+
const back = svgToStrokes(svg);
|
|
217
|
+
const txt = back.find((s) => s.tool === 'text') as TextStroke;
|
|
218
|
+
expect(txt.anchorId).toBe('poly1');
|
|
219
|
+
expect(strokesToSvg(back)).toBe(svg);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('shape-kind conversion (Wave H)', () => {
|
|
224
|
+
const base: RectStroke = {
|
|
225
|
+
id: 's_conv',
|
|
226
|
+
tool: 'rect',
|
|
227
|
+
color: '#3b82f6',
|
|
228
|
+
width: 3,
|
|
229
|
+
x: 10,
|
|
230
|
+
y: 20,
|
|
231
|
+
w: 100,
|
|
232
|
+
h: 60,
|
|
233
|
+
fill: '#e0ebfd',
|
|
234
|
+
rotation: 30,
|
|
235
|
+
groupIds: ['g_1'],
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
test('shapeKindOf maps every closed shape; null otherwise', () => {
|
|
239
|
+
expect(shapeKindOf(base)).toBe('square');
|
|
240
|
+
expect(shapeKindOf({ ...base, cornerRadius: 8 })).toBe('rounded');
|
|
241
|
+
expect(
|
|
242
|
+
shapeKindOf({ id: 'e', tool: 'ellipse', color: '#000', width: 2, cx: 0, cy: 0, rx: 5, ry: 5 })
|
|
243
|
+
).toBe('circle');
|
|
244
|
+
expect(
|
|
245
|
+
shapeKindOf({
|
|
246
|
+
id: 'p',
|
|
247
|
+
tool: 'polygon',
|
|
248
|
+
shape: 'diamond',
|
|
249
|
+
color: '#000',
|
|
250
|
+
width: 2,
|
|
251
|
+
x: 0,
|
|
252
|
+
y: 0,
|
|
253
|
+
w: 10,
|
|
254
|
+
h: 10,
|
|
255
|
+
})
|
|
256
|
+
).toBe('diamond');
|
|
257
|
+
expect(shapeKindOf({ id: 't', tool: 'text', color: '#000', fontSize: 14, text: 'x' })).toBe(
|
|
258
|
+
null
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('rect → circle preserves bbox, styling, rotation, groups, id', () => {
|
|
263
|
+
const patch = convertShapeKind(base, 'circle');
|
|
264
|
+
expect(patch).toMatchObject({ tool: 'ellipse', cx: 60, cy: 50, rx: 50, ry: 30 });
|
|
265
|
+
const converted = { ...base, ...patch } as Stroke;
|
|
266
|
+
// Styling + identity ride along via the merge; stale rect geometry cleared.
|
|
267
|
+
expect(converted.id).toBe('s_conv');
|
|
268
|
+
expect(converted.color).toBe('#3b82f6');
|
|
269
|
+
expect((converted as { fill?: string | null }).fill).toBe('#e0ebfd');
|
|
270
|
+
expect(converted.rotation).toBe(30);
|
|
271
|
+
expect(converted.groupIds).toEqual(['g_1']);
|
|
272
|
+
expect((converted as { x?: number }).x).toBeUndefined();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('circle → diamond → square round-trips the bbox', () => {
|
|
276
|
+
const circle = {
|
|
277
|
+
id: 'e2',
|
|
278
|
+
tool: 'ellipse',
|
|
279
|
+
color: '#000',
|
|
280
|
+
width: 2,
|
|
281
|
+
cx: 60,
|
|
282
|
+
cy: 50,
|
|
283
|
+
rx: 50,
|
|
284
|
+
ry: 30,
|
|
285
|
+
} as Stroke;
|
|
286
|
+
const toDiamond = { ...circle, ...convertShapeKind(circle, 'diamond') } as Stroke;
|
|
287
|
+
expect(toDiamond).toMatchObject({
|
|
288
|
+
tool: 'polygon',
|
|
289
|
+
shape: 'diamond',
|
|
290
|
+
x: 10,
|
|
291
|
+
y: 20,
|
|
292
|
+
w: 100,
|
|
293
|
+
h: 60,
|
|
294
|
+
});
|
|
295
|
+
const toSquare = { ...toDiamond, ...convertShapeKind(toDiamond, 'square') } as Stroke;
|
|
296
|
+
expect(toSquare).toMatchObject({ tool: 'rect', x: 10, y: 20, w: 100, h: 60, cornerRadius: 0 });
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test('identity + non-shape conversions return null', () => {
|
|
300
|
+
expect(convertShapeKind(base, 'square')).toBe(null);
|
|
301
|
+
expect(
|
|
302
|
+
convertShapeKind({ id: 't', tool: 'text', color: '#000', fontSize: 14, text: 'x' }, 'circle')
|
|
303
|
+
).toBe(null);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('converted stroke serializes cleanly and round-trips', () => {
|
|
307
|
+
const converted = { ...base, ...convertShapeKind(base, 'triangle') } as Stroke;
|
|
308
|
+
const svg = strokesToSvg([converted]);
|
|
309
|
+
expect(svg).toContain('data-shape="triangle"');
|
|
310
|
+
expect(strokesToSvg(svgToStrokes(svg))).toBe(svg);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe('non-finite geometry hardening (Wave H — security F4)', () => {
|
|
315
|
+
test('Infinity / NaN / non-numeric coords clamp to 0 on parse (no off-board spoof)', () => {
|
|
316
|
+
// A poisoned/synced SVG carries x="1e999" (→ Infinity) and a non-numeric y.
|
|
317
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" data-mdcc-annotations="1"><rect data-id="s_evil" data-tool="rect" stroke="#1f1f1f" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" vector-effect="non-scaling-stroke" fill="none" x="1e999" y="abc" width="100" height="50"/></svg>`;
|
|
318
|
+
const rect = svgToStrokes(svg)[0] as RectStroke;
|
|
319
|
+
expect(rect).toBeDefined();
|
|
320
|
+
expect(Number.isFinite(rect.x)).toBe(true);
|
|
321
|
+
expect(Number.isFinite(rect.y)).toBe(true);
|
|
322
|
+
expect(rect.x).toBe(0);
|
|
323
|
+
expect(rect.y).toBe(0);
|
|
324
|
+
// The bbox the AI reader + the renderer both consume is finite — they agree.
|
|
325
|
+
const bb = strokeBBox(rect);
|
|
326
|
+
expect(bb && Number.isFinite(bb.x) && Number.isFinite(bb.w)).toBe(true);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test('polygon with an Infinity vertex does not blow the bbox to Infinity', () => {
|
|
330
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" data-mdcc-annotations="1"><polygon data-id="s_p" data-tool="polygon" stroke="#1f1f1f" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" vector-effect="non-scaling-stroke" fill="none" data-shape="diamond" points="50,0 1e999,25 50,50 0,25"/></svg>`;
|
|
331
|
+
const poly = svgToStrokes(svg)[0] as PolygonStroke;
|
|
332
|
+
if (poly) {
|
|
333
|
+
expect(Number.isFinite(poly.w)).toBe(true);
|
|
334
|
+
expect(Number.isFinite(poly.x)).toBe(true);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test('finite geometry is untouched (round-trip stays byte-identical)', () => {
|
|
339
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" data-mdcc-annotations="1"><rect data-id="s_ok" data-tool="rect" stroke="#1f1f1f" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" vector-effect="non-scaling-stroke" fill="none" x="12.5" y="-3" width="100" height="50"/></svg>`;
|
|
340
|
+
expect(strokesToSvg(svgToStrokes(svg))).toBe(svg);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" data-mdcc-annotations="1"><rect data-id="s_aaa1" data-tool="rect" stroke="#3b82f6" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" vector-effect="non-scaling-stroke" fill="#e0ebfd" x="100" y="100" width="160" height="100" data-group-ids="g_team1"/><g data-id="s_aaa2" data-tool="sticky" data-r="8" data-fs="14" fill="#fce8a6" data-group-ids="g_inner g_team1"><rect x="320" y="100" width="200" height="200" rx="8" ry="8"/><text data-sticky-body="1" x="332" y="112" font-size="14" fill="#1a1a1a" dominant-baseline="hanging">flow start</text></g><g data-id="s_aaa3" data-tool="arrow" stroke="#1f1f1f" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" vector-effect="non-scaling-stroke" fill="none" data-start-bind="s_aaa1 1 0.5" data-end-bind="s_aaa2 0 0.5 p"><line x1="260" y1="150" x2="320" y2="200"/><polyline points="307.1972004393032,196.78622909829892 320,200 314.5302123070536,187.9866148569984" fill="#1f1f1f"/></g><text data-id="s_aaa4" data-tool="text" x="100" y="260" data-font-size="14" fill="#1f1f1f" text-anchor="start" dominant-baseline="hanging" data-author="ai">AI note</text><ellipse data-id="s_aaa5" data-tool="ellipse" stroke="#30a46c" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" vector-effect="non-scaling-stroke" fill="none" cx="700" cy="150" rx="60" ry="40" data-rot="15" transform="rotate(15 700 150)"/></svg>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// DDR-093 — `/_index-data` attaches a per-canvas design-system map so the
|
|
2
|
+
// client's canvasUrl() can inject each UI canvas's OWN tokens instead of always
|
|
3
|
+
// designSystems[0]. This is the server half of the
|
|
4
|
+
// "non-default-DS canvas renders unstyled" fix.
|
|
5
|
+
|
|
6
|
+
import { describe, expect, test } from 'bun:test';
|
|
7
|
+
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
|
|
11
|
+
import { bootServer, killProc, nextPort } from './_helpers.ts';
|
|
12
|
+
|
|
13
|
+
// A two-DS sandbox: `project` is the default (ds0, rootClass mdcc), `maude` is
|
|
14
|
+
// the non-default one (rootClass maude) the bug broke.
|
|
15
|
+
function makeMultiDsSandbox(): { root: string; designRoot: string } {
|
|
16
|
+
const root = mkdtempSync(join(tmpdir(), 'mdcc-multids-'));
|
|
17
|
+
const designRoot = join(root, '.design');
|
|
18
|
+
mkdirSync(designRoot, { recursive: true });
|
|
19
|
+
writeFileSync(
|
|
20
|
+
join(designRoot, 'config.json'),
|
|
21
|
+
JSON.stringify(
|
|
22
|
+
{
|
|
23
|
+
name: 'multi-ds-test',
|
|
24
|
+
designRoot: '.design',
|
|
25
|
+
canvasGroups: [
|
|
26
|
+
{ label: 'Design system', path: 'system' },
|
|
27
|
+
{ label: 'UI kit', path: 'ui' },
|
|
28
|
+
],
|
|
29
|
+
designSystems: [
|
|
30
|
+
{ name: 'project', path: 'system/project', rootClass: 'mdcc' },
|
|
31
|
+
{ name: 'maude', path: 'system/maude', rootClass: 'maude' },
|
|
32
|
+
],
|
|
33
|
+
defaultDesignSystem: 'project',
|
|
34
|
+
},
|
|
35
|
+
null,
|
|
36
|
+
2
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const tsx = 'export default function C(){return <main/>}\n';
|
|
41
|
+
|
|
42
|
+
// UI canvas authored under the NON-default DS — declares it in its sidecar.
|
|
43
|
+
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
44
|
+
writeFileSync(join(designRoot, 'ui', 'Studio.tsx'), tsx);
|
|
45
|
+
writeFileSync(
|
|
46
|
+
join(designRoot, 'ui', 'Studio.meta.json'),
|
|
47
|
+
JSON.stringify({ designSystem: 'maude' })
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// UI canvas with a sidecar that omits designSystem → resolves to the default.
|
|
51
|
+
writeFileSync(join(designRoot, 'ui', 'Plain.tsx'), tsx);
|
|
52
|
+
writeFileSync(join(designRoot, 'ui', 'Plain.meta.json'), JSON.stringify({ title: 'Plain' }));
|
|
53
|
+
|
|
54
|
+
// UI canvas with NO sidecar at all → also defaults.
|
|
55
|
+
writeFileSync(join(designRoot, 'ui', 'Bare.tsx'), tsx);
|
|
56
|
+
|
|
57
|
+
// Specimens — DS resolved path-authoritatively (no sidecar needed).
|
|
58
|
+
mkdirSync(join(designRoot, 'system', 'maude', 'preview'), { recursive: true });
|
|
59
|
+
writeFileSync(join(designRoot, 'system', 'maude', 'preview', 'Buttons.tsx'), tsx);
|
|
60
|
+
mkdirSync(join(designRoot, 'system', 'project', 'preview'), { recursive: true });
|
|
61
|
+
writeFileSync(join(designRoot, 'system', 'project', 'preview', 'Cards.tsx'), tsx);
|
|
62
|
+
|
|
63
|
+
return { root, designRoot };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface IndexData {
|
|
67
|
+
canvasDesignSystems?: Record<string, string>;
|
|
68
|
+
groups?: { paths?: string[] }[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe('/_index-data — per-canvas design-system map', () => {
|
|
72
|
+
test('attaches the right DS for UI canvases (sidecar) and specimens (path)', async () => {
|
|
73
|
+
const { root } = makeMultiDsSandbox();
|
|
74
|
+
const port = nextPort();
|
|
75
|
+
const proc = await bootServer(root, port);
|
|
76
|
+
try {
|
|
77
|
+
const r = await fetch(`http://localhost:${port}/_index-data`);
|
|
78
|
+
expect(r.status).toBe(200);
|
|
79
|
+
const data = (await r.json()) as IndexData;
|
|
80
|
+
const map = data.canvasDesignSystems ?? {};
|
|
81
|
+
|
|
82
|
+
// The bug: this used to be implicitly designSystems[0] ('project'). The
|
|
83
|
+
// sidecar declares 'maude', so the server must surface 'maude'.
|
|
84
|
+
expect(map['.design/ui/Studio.tsx']).toBe('maude');
|
|
85
|
+
|
|
86
|
+
// Sidecar without designSystem, and no sidecar at all → project default.
|
|
87
|
+
expect(map['.design/ui/Plain.tsx']).toBe('project');
|
|
88
|
+
expect(map['.design/ui/Bare.tsx']).toBe('project');
|
|
89
|
+
|
|
90
|
+
// Specimens resolve from their `system/<ds>/` folder.
|
|
91
|
+
expect(map['.design/system/maude/preview/Buttons.tsx']).toBe('maude');
|
|
92
|
+
expect(map['.design/system/project/preview/Cards.tsx']).toBe('project');
|
|
93
|
+
} finally {
|
|
94
|
+
await killProc(proc);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Unit cover for the live-motion proof sampler's pure delta test. The browser
|
|
2
|
+
// drive itself is integration-shape (needs Chromium + a running dev server);
|
|
3
|
+
// here we pin the `signatureChanged` classifier that decides motion-proven vs
|
|
4
|
+
// dead-mechanism, so a regression can't silently turn a freeze-frame "pass"
|
|
5
|
+
// into a false motion confirmation (DDR-094 M1).
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test } from 'bun:test';
|
|
8
|
+
import { signatureChanged } from '../bin/_motion-sample-playwright.mjs';
|
|
9
|
+
|
|
10
|
+
const base = { x: 10, y: 10, width: 80, height: 80, transform: 'none', opacity: 1 };
|
|
11
|
+
|
|
12
|
+
describe('signatureChanged', () => {
|
|
13
|
+
test('identical samples ⇒ no change (dead mechanism)', () => {
|
|
14
|
+
expect(signatureChanged(base, { ...base })).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
test('a bbox shift (morph/scale/translate) ⇒ change', () => {
|
|
17
|
+
expect(signatureChanged(base, { ...base, width: 92 })).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
test('a transform-matrix change ⇒ change (rotate/scale via CSS)', () => {
|
|
20
|
+
expect(signatureChanged(base, { ...base, transform: 'matrix(1,0.2,0,1,0,0)' })).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
test('an opacity fade ⇒ change (no bbox delta)', () => {
|
|
23
|
+
expect(signatureChanged(base, { ...base, opacity: 0.4 })).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
test('sub-epsilon numeric jitter is NOT a change (anti-flake)', () => {
|
|
26
|
+
expect(signatureChanged(base, { ...base, x: 10.1 })).toBe(false);
|
|
27
|
+
expect(signatureChanged(base, { ...base, x: 10.1 }, 0.05)).toBe(true); // tighter eps catches it
|
|
28
|
+
});
|
|
29
|
+
test('the internal __missing marker never counts as a change', () => {
|
|
30
|
+
expect(signatureChanged({ __missing: true }, { __missing: true })).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
test('a null sample is not a change (fail closed)', () => {
|
|
33
|
+
expect(signatureChanged(null, base)).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// Performance harness for Phase 3.4 budgets.
|
|
3
3
|
//
|
|
4
|
-
// bun run
|
|
4
|
+
// bun run apps/studio/test/perf-harness.ts
|
|
5
5
|
//
|
|
6
6
|
// Boots the dev-server against a synthetic .design/ fixture, measures the
|
|
7
7
|
// budget table from `.ai/plans/phase-3.4-architecture-refactor.md`, and writes
|
|
@@ -19,7 +19,9 @@ import { buildCanvasModule } from '../canvas-build.ts';
|
|
|
19
19
|
import { transpileCanvasSource } from '../canvas-pipeline.ts';
|
|
20
20
|
import { emitRegistryItem } from '../handoff.ts';
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
// apps/studio/test → ../../.. = repo root (DDR-095: was ../../../.. at the old
|
|
23
|
+
// plugins/design/dev-server/test depth).
|
|
24
|
+
const REPO_ROOT = path.resolve(import.meta.dir, '../../..');
|
|
23
25
|
const DESIGN_ROOT = path.join(REPO_ROOT, '.design');
|
|
24
26
|
const DOCS_SITE = path.join(DESIGN_ROOT, 'ui/Docs Site.tsx');
|
|
25
27
|
const CANVAS_VIEWPORT = path.join(DESIGN_ROOT, 'ui/Canvas Viewport.tsx');
|