@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
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import {
|
|
3
|
+
type CubicBezierHandles,
|
|
4
|
+
easeBezier,
|
|
5
|
+
LINEAR,
|
|
6
|
+
lerpValue,
|
|
7
|
+
lerpVertices,
|
|
8
|
+
parallel,
|
|
9
|
+
resolveStagger,
|
|
10
|
+
sampleTrack,
|
|
11
|
+
sequence,
|
|
12
|
+
shiftTrack,
|
|
13
|
+
stagger,
|
|
14
|
+
type Track,
|
|
15
|
+
timeline,
|
|
16
|
+
track,
|
|
17
|
+
trackSpan,
|
|
18
|
+
} from '../animate.ts';
|
|
19
|
+
|
|
20
|
+
describe('easeBezier', () => {
|
|
21
|
+
test('linear is the identity', () => {
|
|
22
|
+
for (const x of [0, 0.25, 0.5, 0.75, 1]) {
|
|
23
|
+
expect(easeBezier(LINEAR, x)).toBeCloseTo(x, 6);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
test('clamps the endpoints exactly', () => {
|
|
27
|
+
const e: CubicBezierHandles = [0.42, 0, 0.58, 1];
|
|
28
|
+
expect(easeBezier(e, 0)).toBe(0);
|
|
29
|
+
expect(easeBezier(e, 1)).toBe(1);
|
|
30
|
+
expect(easeBezier(e, -0.5)).toBe(0);
|
|
31
|
+
expect(easeBezier(e, 1.5)).toBe(1);
|
|
32
|
+
});
|
|
33
|
+
test('ease-in-out is symmetric about the midpoint and monotone', () => {
|
|
34
|
+
const e: CubicBezierHandles = [0.42, 0, 0.58, 1];
|
|
35
|
+
expect(easeBezier(e, 0.5)).toBeCloseTo(0.5, 4);
|
|
36
|
+
let prev = -1;
|
|
37
|
+
for (let i = 0; i <= 20; i++) {
|
|
38
|
+
const y = easeBezier(e, i / 20);
|
|
39
|
+
expect(y).toBeGreaterThanOrEqual(prev - 1e-9);
|
|
40
|
+
prev = y;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
test('preserves overshoot (y > 1) for a snap easing', () => {
|
|
44
|
+
const snap: CubicBezierHandles = [0.34, 1.42, 0.64, 1];
|
|
45
|
+
let maxY = 0;
|
|
46
|
+
for (let i = 0; i <= 100; i++) maxY = Math.max(maxY, easeBezier(snap, i / 100));
|
|
47
|
+
expect(maxY).toBeGreaterThan(1); // the overshoot the snap depends on
|
|
48
|
+
});
|
|
49
|
+
test('round-trips a known x→t→y solve (ease-out)', () => {
|
|
50
|
+
// cubic-bezier(0,0,0.58,1): solving x then evaluating y must land in (x,1).
|
|
51
|
+
const e: CubicBezierHandles = [0, 0, 0.58, 1];
|
|
52
|
+
const y = easeBezier(e, 0.5);
|
|
53
|
+
expect(y).toBeGreaterThan(0.5); // ease-out is ahead of linear at the midpoint
|
|
54
|
+
expect(y).toBeLessThan(1);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('lerpValue / lerpVertices', () => {
|
|
59
|
+
test('numbers lerp', () => {
|
|
60
|
+
expect(lerpValue(0, 10, 0.3)).toBeCloseTo(3, 6);
|
|
61
|
+
});
|
|
62
|
+
test('number arrays lerp element-wise', () => {
|
|
63
|
+
expect(lerpValue([0, 10], [10, 0], 0.5)).toEqual([5, 5]);
|
|
64
|
+
});
|
|
65
|
+
test('vertex arrays lerp by component', () => {
|
|
66
|
+
const out = lerpVertices([{ x: 0, y: 0 }], [{ x: 4, y: 8 }], 0.25);
|
|
67
|
+
expect(out).toEqual([{ x: 1, y: 2 }]);
|
|
68
|
+
});
|
|
69
|
+
test('strings are a discrete hold (no interpolation)', () => {
|
|
70
|
+
expect(lerpValue('rotate(0)', 'rotate(90)', 0.5)).toBe('rotate(0)');
|
|
71
|
+
expect(lerpValue('rotate(0)', 'rotate(90)', 1)).toBe('rotate(90)');
|
|
72
|
+
});
|
|
73
|
+
test('vertex count mismatch throws (interpolability constraint)', () => {
|
|
74
|
+
expect(() => lerpVertices([{ x: 0, y: 0 }], [], 0.5)).toThrow(/vertex count mismatch/);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('sampleTrack', () => {
|
|
79
|
+
const tr = track('opacity', [
|
|
80
|
+
{ t: 0, value: 0, ease: LINEAR },
|
|
81
|
+
{ t: 1, value: 1 },
|
|
82
|
+
]);
|
|
83
|
+
test('holds before the first and after the last keyframe', () => {
|
|
84
|
+
expect(sampleTrack(tr, -1)).toBe(0);
|
|
85
|
+
expect(sampleTrack(tr, 5)).toBe(1);
|
|
86
|
+
});
|
|
87
|
+
test('linear interpolation mid-segment', () => {
|
|
88
|
+
expect(sampleTrack(tr, 0.5)).toBeCloseTo(0.5, 6);
|
|
89
|
+
});
|
|
90
|
+
test('applies the leading keyframe easing', () => {
|
|
91
|
+
const eased = track('opacity', [
|
|
92
|
+
{ t: 0, value: 0, ease: [0, 0, 0.58, 1] }, // ease-out
|
|
93
|
+
{ t: 1, value: 1 },
|
|
94
|
+
]);
|
|
95
|
+
expect(sampleTrack(eased, 0.5) as number).toBeGreaterThan(0.5);
|
|
96
|
+
});
|
|
97
|
+
test('empty track samples undefined', () => {
|
|
98
|
+
expect(sampleTrack(track('opacity', []), 0)).toBeUndefined();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('combinators', () => {
|
|
103
|
+
const a = (): Track =>
|
|
104
|
+
track(
|
|
105
|
+
'opacity',
|
|
106
|
+
[
|
|
107
|
+
{ t: 0, value: 0 },
|
|
108
|
+
{ t: 1, value: 1 },
|
|
109
|
+
],
|
|
110
|
+
'a'
|
|
111
|
+
);
|
|
112
|
+
const b = (): Track =>
|
|
113
|
+
track(
|
|
114
|
+
'opacity',
|
|
115
|
+
[
|
|
116
|
+
{ t: 0, value: 1 },
|
|
117
|
+
{ t: 2, value: 0 },
|
|
118
|
+
],
|
|
119
|
+
'b'
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
test('trackSpan is the latest keyframe time', () => {
|
|
123
|
+
expect(trackSpan([a(), b()])).toBe(2);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('shiftTrack offsets every keyframe', () => {
|
|
127
|
+
const s = shiftTrack(a(), 3);
|
|
128
|
+
expect(s.keyframes.map((k) => k.t)).toEqual([3, 4]);
|
|
129
|
+
expect(a().keyframes[0].t).toBe(0); // immutable
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('stagger offsets track i by i·delay', () => {
|
|
133
|
+
const out = stagger([a(), b(), a()], 0.5);
|
|
134
|
+
expect(out[0].keyframes[0].t).toBe(0);
|
|
135
|
+
expect(out[1].keyframes[0].t).toBe(0.5);
|
|
136
|
+
expect(out[2].keyframes[0].t).toBe(1);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('resolveStagger bakes the declarative field and grows dur', () => {
|
|
140
|
+
const tl = timeline({ tracks: [a(), b()], dur: 2, stagger: 0.5 });
|
|
141
|
+
const r = resolveStagger(tl);
|
|
142
|
+
expect(r.stagger).toBeUndefined();
|
|
143
|
+
expect(r.tracks[1].keyframes[0].t).toBe(0.5);
|
|
144
|
+
expect(r.dur).toBe(2.5); // b ended at 2 → shifted to 2.5
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('parallel unions tracks and takes the max duration', () => {
|
|
148
|
+
const tl = parallel(
|
|
149
|
+
timeline({ tracks: [a()], dur: 1 }),
|
|
150
|
+
timeline({ tracks: [b()], dur: 2, begin: 0.5 })
|
|
151
|
+
);
|
|
152
|
+
expect(tl.tracks.length).toBe(2);
|
|
153
|
+
expect(tl.dur).toBe(2.5); // 0.5 begin + 2 dur
|
|
154
|
+
expect(tl.tracks[1].keyframes[0].t).toBe(0.5); // b shifted by its begin
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('sequence chains timelines end-to-start and sums durations', () => {
|
|
158
|
+
const tl = sequence(timeline({ tracks: [a()], dur: 1 }), timeline({ tracks: [b()], dur: 2 }));
|
|
159
|
+
expect(tl.dur).toBe(3);
|
|
160
|
+
expect(tl.tracks[0].keyframes.map((k) => k.t)).toEqual([0, 1]);
|
|
161
|
+
expect(tl.tracks[1].keyframes.map((k) => k.t)).toEqual([1, 3]); // shifted by first dur
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('sequence honors a per-timeline begin gap', () => {
|
|
165
|
+
const tl = sequence(
|
|
166
|
+
timeline({ tracks: [a()], dur: 1 }),
|
|
167
|
+
timeline({ tracks: [a()], dur: 1, begin: 0.5 })
|
|
168
|
+
);
|
|
169
|
+
expect(tl.tracks[1].keyframes[0].t).toBe(1.5); // 1 (cursor) + 0.5 (gap)
|
|
170
|
+
expect(tl.dur).toBe(2.5);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { sampleTrack } from '../animate.ts';
|
|
3
|
+
import { blobPath } from '../geometry.ts';
|
|
4
|
+
import { morphVariants, parseMorphPath, templateToPath } from '../morph.ts';
|
|
5
|
+
import type { Point } from '../primitives.ts';
|
|
6
|
+
|
|
7
|
+
describe('parseMorphPath', () => {
|
|
8
|
+
test('extracts vertices + a command template (M…C…Z)', () => {
|
|
9
|
+
const { template, vertices } = parseMorphPath('M0 0C1 1 2 2 3 3Z');
|
|
10
|
+
expect(template.commands.map((c) => c.cmd)).toEqual(['M', 'C', 'Z']);
|
|
11
|
+
expect(template.vertexCount).toBe(4); // M=1 + C=3
|
|
12
|
+
expect(vertices).toEqual([
|
|
13
|
+
{ x: 0, y: 0 },
|
|
14
|
+
{ x: 1, y: 1 },
|
|
15
|
+
{ x: 2, y: 2 },
|
|
16
|
+
{ x: 3, y: 3 },
|
|
17
|
+
]);
|
|
18
|
+
});
|
|
19
|
+
test('round-trips through templateToPath', () => {
|
|
20
|
+
const d = 'M0 0L10 0L10 10L0 10Z';
|
|
21
|
+
const { template, vertices } = parseMorphPath(d);
|
|
22
|
+
expect(templateToPath(template, vertices)).toBe(d);
|
|
23
|
+
});
|
|
24
|
+
test('parses negative + decimal + exponent numbers', () => {
|
|
25
|
+
const { vertices } = parseMorphPath('M-1.5 2e1L3 -4.25');
|
|
26
|
+
expect(vertices).toEqual([
|
|
27
|
+
{ x: -1.5, y: 20 },
|
|
28
|
+
{ x: 3, y: -4.25 },
|
|
29
|
+
]);
|
|
30
|
+
});
|
|
31
|
+
test('rejects non-morph-safe commands (H/V/A)', () => {
|
|
32
|
+
expect(() => parseMorphPath('M0 0H10')).toThrow(/morph-safe/);
|
|
33
|
+
expect(() => parseMorphPath('M0 0A5 5 0 0 1 10 10')).toThrow(/morph-safe/);
|
|
34
|
+
});
|
|
35
|
+
test('rejects relative (lowercase) commands', () => {
|
|
36
|
+
expect(() => parseMorphPath('M0 0l10 10')).toThrow(/morph-safe/);
|
|
37
|
+
});
|
|
38
|
+
test('templateToPath enforces the vertex-count invariant', () => {
|
|
39
|
+
const { template } = parseMorphPath('M0 0L1 1');
|
|
40
|
+
expect(() => templateToPath(template, [{ x: 0, y: 0 }])).toThrow(/vertex count/);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('morphVariants', () => {
|
|
45
|
+
const base = blobPath(50, 50, 30, { lobes: 7, seed: 3 });
|
|
46
|
+
|
|
47
|
+
test('every keyframe shares the fixed vertex count (interpolability)', () => {
|
|
48
|
+
const { track, template } = morphVariants(base, { n: 4, jitter: 3, seed: 7 });
|
|
49
|
+
for (const kf of track.keyframes) {
|
|
50
|
+
expect((kf.value as Point[]).length).toBe(template.vertexCount);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('is deterministic for a given seed', () => {
|
|
55
|
+
const a = morphVariants(base, { n: 3, jitter: 4, seed: 9 });
|
|
56
|
+
const b = morphVariants(base, { n: 3, jitter: 4, seed: 9 });
|
|
57
|
+
expect(a.track.keyframes).toEqual(b.track.keyframes);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('different seeds produce different jitter', () => {
|
|
61
|
+
const a = morphVariants(base, { n: 3, jitter: 4, seed: 1 });
|
|
62
|
+
const b = morphVariants(base, { n: 3, jitter: 4, seed: 2 });
|
|
63
|
+
expect(a.track.keyframes[1].value).not.toEqual(b.track.keyframes[1].value);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('jitter 0 keeps every keyframe equal to the base shape', () => {
|
|
67
|
+
const { track } = morphVariants(base, { n: 3, jitter: 0, seed: 5 });
|
|
68
|
+
const first = track.keyframes[0].value;
|
|
69
|
+
for (const kf of track.keyframes) expect(kf.value).toEqual(first);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('loop (default) returns to the base on the final keyframe', () => {
|
|
73
|
+
const { track } = morphVariants(base, { n: 3, jitter: 5, seed: 5 });
|
|
74
|
+
const kfs = track.keyframes;
|
|
75
|
+
expect(kfs[kfs.length - 1].value).toEqual(kfs[0].value); // seamless repeat
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('loop:false drops the closing base keyframe', () => {
|
|
79
|
+
const loop = morphVariants(base, { n: 3, jitter: 5, loop: true });
|
|
80
|
+
const open = morphVariants(base, { n: 3, jitter: 5, loop: false });
|
|
81
|
+
expect(open.track.keyframes.length).toBe(loop.track.keyframes.length - 1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('keyframes span [0, dur] evenly', () => {
|
|
85
|
+
const { track } = morphVariants(base, { n: 2, dur: 2 });
|
|
86
|
+
const ts = track.keyframes.map((k) => k.t);
|
|
87
|
+
expect(ts[0]).toBe(0);
|
|
88
|
+
expect(ts[ts.length - 1]).toBe(2);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('sampling + toPath yields a valid morph-safe d at mid-timeline', () => {
|
|
92
|
+
const { track, toPath } = morphVariants(base, { n: 3, jitter: 4, dur: 1, seed: 2 });
|
|
93
|
+
const mid = sampleTrack(track, 0.5) as Point[];
|
|
94
|
+
const d = toPath(mid);
|
|
95
|
+
expect(d.startsWith('M')).toBe(true);
|
|
96
|
+
// Re-parsing the rendered frame must keep the same vertex count.
|
|
97
|
+
expect(() => parseMorphPath(d)).not.toThrow();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('ease is applied to all but the last keyframe', () => {
|
|
101
|
+
const ease = [0.34, 1.42, 0.64, 1] as const;
|
|
102
|
+
const { track } = morphVariants(base, { n: 2, ease });
|
|
103
|
+
const kfs = track.keyframes;
|
|
104
|
+
for (let i = 0; i < kfs.length - 1; i++) expect(kfs[i].ease).toEqual(ease);
|
|
105
|
+
expect(kfs[kfs.length - 1].ease).toBeUndefined();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { parallel, type Timeline, timeline, track } from '../animate.ts';
|
|
3
|
+
import { morphVariants } from '../morph.ts';
|
|
4
|
+
import { circle, type DrawPrimitive, path } from '../primitives.ts';
|
|
5
|
+
import { buildAnimPlan, toAnimatedJsx, toAnimatedSvg } from '../serialize-animate.ts';
|
|
6
|
+
|
|
7
|
+
// An animated mark: a morphing blob + a blinking eye + a rotating blob.
|
|
8
|
+
const blob = morphVariants('M10 50L50 10L90 50L50 90Z', {
|
|
9
|
+
n: 3,
|
|
10
|
+
jitter: 5,
|
|
11
|
+
seed: 4,
|
|
12
|
+
dur: 1.2,
|
|
13
|
+
ease: [0.34, 1.42, 0.64, 1], // snap with overshoot
|
|
14
|
+
target: 'blob',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const PRIMS: DrawPrimitive[] = [
|
|
18
|
+
path({ d: 'M10 50L50 10L90 50L50 90Z', id: 'blob', fill: '#e94' }),
|
|
19
|
+
circle({ cx: 50, cy: 45, r: 6, id: 'eye', fill: '#222' }),
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const TL: Timeline = parallel(
|
|
23
|
+
timeline({ tracks: [blob.track], repeat: 'indefinite' }),
|
|
24
|
+
timeline({
|
|
25
|
+
tracks: [
|
|
26
|
+
track(
|
|
27
|
+
'opacity',
|
|
28
|
+
[
|
|
29
|
+
{ t: 0, value: 1, ease: [0.42, 0, 0.58, 1] },
|
|
30
|
+
{ t: 0.5, value: 0.1 },
|
|
31
|
+
{ t: 1, value: 1 },
|
|
32
|
+
],
|
|
33
|
+
'eye'
|
|
34
|
+
),
|
|
35
|
+
track(
|
|
36
|
+
'rotate',
|
|
37
|
+
[
|
|
38
|
+
{ t: 0, value: 0 },
|
|
39
|
+
{ t: 1.2, value: 360 },
|
|
40
|
+
],
|
|
41
|
+
'blob'
|
|
42
|
+
),
|
|
43
|
+
],
|
|
44
|
+
repeat: 'indefinite',
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const OPTS = { viewBox: '0 0 100 100', pathRenderers: { blob: blob.toPath } };
|
|
49
|
+
|
|
50
|
+
describe('buildAnimPlan', () => {
|
|
51
|
+
const plan = buildAnimPlan(TL, OPTS);
|
|
52
|
+
test('one entry per targeted, ≥2-keyframe track', () => {
|
|
53
|
+
expect(plan.length).toBe(3);
|
|
54
|
+
});
|
|
55
|
+
test('d-morph maps to <animate attributeName=d>', () => {
|
|
56
|
+
const d = plan.find((e) => e.property === 'd');
|
|
57
|
+
expect(d?.kind).toBe('animate');
|
|
58
|
+
expect(d?.attr).toBe('d');
|
|
59
|
+
expect(d?.values[0].startsWith('M')).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
test('rotate maps to animateTransform', () => {
|
|
62
|
+
const r = plan.find((e) => e.property === 'rotate');
|
|
63
|
+
expect(r?.kind).toBe('animateTransform');
|
|
64
|
+
expect(r?.attr).toBe('rotate');
|
|
65
|
+
});
|
|
66
|
+
test('keyTimes are normalized 0→1', () => {
|
|
67
|
+
for (const e of plan) {
|
|
68
|
+
expect(e.keyTimes[0]).toBe(0);
|
|
69
|
+
expect(e.keyTimes[e.keyTimes.length - 1]).toBe(1);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
test('eased track carries n-1 keySplines', () => {
|
|
73
|
+
const op = plan.find((e) => e.property === 'opacity');
|
|
74
|
+
expect(op).toBeDefined();
|
|
75
|
+
expect(op?.keySplines?.length).toBe((op?.values.length ?? 0) - 1);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('toAnimatedSvg (SMIL)', () => {
|
|
80
|
+
const svg = toAnimatedSvg(PRIMS, TL, OPTS);
|
|
81
|
+
test('injects SMIL d-morph with spline easing', () => {
|
|
82
|
+
expect(svg).toContain('<animate attributeName="d"');
|
|
83
|
+
expect(svg).toContain('calcMode="spline"');
|
|
84
|
+
expect(svg).toContain('keySplines=');
|
|
85
|
+
});
|
|
86
|
+
test('rotate is an additive animateTransform (no static-transform clobber)', () => {
|
|
87
|
+
expect(svg).toMatch(/<animateTransform[^>]*type="rotate"[^>]*additive="sum"/);
|
|
88
|
+
});
|
|
89
|
+
test('preserves the overshoot handle verbatim in keySplines', () => {
|
|
90
|
+
expect(svg).toContain('0.34 1.42 0.64 1');
|
|
91
|
+
});
|
|
92
|
+
test('emits a prefers-reduced-motion gate', () => {
|
|
93
|
+
expect(svg).toContain('prefers-reduced-motion: reduce');
|
|
94
|
+
});
|
|
95
|
+
test('is a complete SVG document', () => {
|
|
96
|
+
expect(svg).toContain('xmlns="http://www.w3.org/2000/svg"');
|
|
97
|
+
expect(svg).toContain('repeatCount="indefinite"');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('toAnimatedJsx (single-source parity)', () => {
|
|
102
|
+
const svg = toAnimatedSvg(PRIMS, TL, OPTS);
|
|
103
|
+
const jsx = toAnimatedJsx(PRIMS, TL, OPTS);
|
|
104
|
+
const count = (s: string, re: RegExp) => (s.match(re) ?? []).length;
|
|
105
|
+
|
|
106
|
+
test('same count of <animate> elements as the SVG form', () => {
|
|
107
|
+
expect(count(jsx, /<animate /g)).toBe(count(svg, /<animate /g));
|
|
108
|
+
});
|
|
109
|
+
test('same count of <animateTransform> elements as the SVG form', () => {
|
|
110
|
+
expect(count(jsx, /<animateTransform /g)).toBe(count(svg, /<animateTransform /g));
|
|
111
|
+
});
|
|
112
|
+
test('omits the xmlns document marker', () => {
|
|
113
|
+
expect(jsx).not.toContain('xmlns=');
|
|
114
|
+
});
|
|
115
|
+
test('keeps the SMIL animation attributes (host renders SMIL)', () => {
|
|
116
|
+
expect(jsx).toContain('attributeName="d"');
|
|
117
|
+
expect(jsx).toContain('keyTimes=');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('fail-loud', () => {
|
|
122
|
+
test('a track targeting a missing id throws', () => {
|
|
123
|
+
const bad = timeline({
|
|
124
|
+
tracks: [
|
|
125
|
+
track(
|
|
126
|
+
'opacity',
|
|
127
|
+
[
|
|
128
|
+
{ t: 0, value: 0 },
|
|
129
|
+
{ t: 1, value: 1 },
|
|
130
|
+
],
|
|
131
|
+
'ghost'
|
|
132
|
+
),
|
|
133
|
+
],
|
|
134
|
+
repeat: 1,
|
|
135
|
+
});
|
|
136
|
+
expect(() => toAnimatedSvg(PRIMS, bad, { viewBox: '0 0 100 100' })).toThrow(/not found/);
|
|
137
|
+
});
|
|
138
|
+
test('a d-morph track without a pathRenderer throws', () => {
|
|
139
|
+
const tl = timeline({ tracks: [blob.track], repeat: 1 });
|
|
140
|
+
expect(() => toAnimatedSvg(PRIMS, tl, { viewBox: '0 0 100 100' })).toThrow(/pathRenderer/);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file equal-spacing-detector.ts — T27 (Wave 3)
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/equal-spacing-detector.ts
|
|
4
4
|
* @purpose Pure detector for Figma "Smart Selection" pink-dot affordance
|
|
5
5
|
* (Rasmus Andersson 2018). Given 3+ rects on a single axis,
|
|
6
6
|
* returns the equal gap + the screen-coord midpoints between
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file equal-spacing-handles.tsx — T27 (Wave 3)
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/equal-spacing-handles.tsx
|
|
4
4
|
* @purpose Figma Smart Selection pink-dot affordance. When 3+ elements are
|
|
5
5
|
* selected AND their bounding rects are equally distributed on an
|
|
6
6
|
* axis, render small pink dots at the midpoints between adjacent
|
|
@@ -14,11 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
import { useEffect, useRef, useState } from 'react';
|
|
16
16
|
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
type EqualSpacingResult,
|
|
20
|
-
type SpacingAxis,
|
|
21
|
-
} from './equal-spacing-detector.ts';
|
|
17
|
+
import { resolveSelectionEl } from './dom-selection.ts';
|
|
18
|
+
import { detectEqualSpacing, type EqualSpacingResult } from './equal-spacing-detector.ts';
|
|
22
19
|
import { useSelectionSet } from './use-selection-set.tsx';
|
|
23
20
|
|
|
24
21
|
const HOVER_PADDING_PX = 40;
|
|
@@ -77,25 +74,6 @@ function ensureStyles(): void {
|
|
|
77
74
|
document.head.appendChild(s);
|
|
78
75
|
}
|
|
79
76
|
|
|
80
|
-
function cssEscape(s: string): string {
|
|
81
|
-
// CSS.escape is available everywhere we care; manual fallback handles the
|
|
82
|
-
// jsdom test path where it may be missing.
|
|
83
|
-
// biome-ignore lint/suspicious/noExplicitAny: feature detect
|
|
84
|
-
const ce = (typeof CSS !== 'undefined' ? (CSS as any).escape : null) as
|
|
85
|
-
| ((v: string) => string)
|
|
86
|
-
| null;
|
|
87
|
-
if (ce) return ce(s);
|
|
88
|
-
return s.replace(/(["\\\]])/g, '\\$1');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function safeQuery(selector: string): Element | null {
|
|
92
|
-
try {
|
|
93
|
-
return document.querySelector(selector);
|
|
94
|
-
} catch {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
77
|
interface ScreenRect {
|
|
100
78
|
x: number;
|
|
101
79
|
y: number;
|
|
@@ -103,12 +81,12 @@ interface ScreenRect {
|
|
|
103
81
|
h: number;
|
|
104
82
|
}
|
|
105
83
|
|
|
106
|
-
function resolveSelectionRects(
|
|
84
|
+
function resolveSelectionRects(
|
|
85
|
+
sels: Array<{ id?: string; selector: string; artboardId?: string | null }>
|
|
86
|
+
): ScreenRect[] {
|
|
107
87
|
const rects: ScreenRect[] = [];
|
|
108
88
|
for (const s of sels) {
|
|
109
|
-
const el = s
|
|
110
|
-
? document.querySelector(`[data-cd-id="${cssEscape(s.id)}"]`)
|
|
111
|
-
: safeQuery(s.selector);
|
|
89
|
+
const el = resolveSelectionEl(document, s);
|
|
112
90
|
if (!el) continue;
|
|
113
91
|
const b = (el as HTMLElement).getBoundingClientRect();
|
|
114
92
|
if (b.width === 0 && b.height === 0) continue;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file export-dialog.tsx — Phase 6.5 T8 export dialog.
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/export-dialog.tsx
|
|
4
4
|
* @purpose Native `<dialog>`-based export modal. Three controls — format,
|
|
5
5
|
* scope, per-format options — plus a Recent tab populated by
|
|
6
6
|
* `/_api/export-history`. Submit fires `POST /_api/export`, the
|
|
@@ -321,6 +321,7 @@ export function ExportDialogProvider({ children }: { children: ReactNode }): Rea
|
|
|
321
321
|
}, [openState, loadHistory]);
|
|
322
322
|
|
|
323
323
|
// ⌘E / Ctrl+E to open; ⌘⇧E / Ctrl+Shift+E to re-run last.
|
|
324
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: re-bind on `open` only — rerunLast is stable per render; re-running on it would tear down the global hotkey listener needlessly.
|
|
324
325
|
useEffect(() => {
|
|
325
326
|
function onKey(e: KeyboardEvent) {
|
|
326
327
|
const mod = e.metaKey || e.ctrlKey;
|
|
@@ -331,7 +332,6 @@ export function ExportDialogProvider({ children }: { children: ReactNode }): Rea
|
|
|
331
332
|
}
|
|
332
333
|
window.addEventListener('keydown', onKey);
|
|
333
334
|
return () => window.removeEventListener('keydown', onKey);
|
|
334
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
335
335
|
}, [open]);
|
|
336
336
|
|
|
337
337
|
// Phase 6.5 T9 — context-menu entries dispatch `maude:open-export` so they
|
|
@@ -339,6 +339,18 @@ export function ExportDialogProvider({ children }: { children: ReactNode }): Rea
|
|
|
339
339
|
useEffect(() => {
|
|
340
340
|
function onCustom(e: Event) {
|
|
341
341
|
const detail = (e as CustomEvent<{ scope?: Scope; format?: Format }>).detail ?? {};
|
|
342
|
+
// Plan C — route to the UNIFIED shell Export dialog (parent / main origin)
|
|
343
|
+
// so the in-canvas toolbar + context menu open the SAME modal as the
|
|
344
|
+
// menubar. Only fall back to this local dialog for a standalone canvas
|
|
345
|
+
// (handoff / exported embed) with no shell parent.
|
|
346
|
+
if (window.parent && window.parent !== window) {
|
|
347
|
+
try {
|
|
348
|
+
window.parent.postMessage({ dgn: 'open-export', detail }, '*');
|
|
349
|
+
return;
|
|
350
|
+
} catch {
|
|
351
|
+
/* fall through to local */
|
|
352
|
+
}
|
|
353
|
+
}
|
|
342
354
|
open(detail);
|
|
343
355
|
}
|
|
344
356
|
window.addEventListener('maude:open-export', onCustom as EventListener);
|
|
@@ -547,8 +559,8 @@ const DialogShell = (() => {
|
|
|
547
559
|
<div className="dc-ed-recent">
|
|
548
560
|
<h3>Recent</h3>
|
|
549
561
|
<ul>
|
|
550
|
-
{history.slice(0, 5).map((h
|
|
551
|
-
<li key={`${h.at}-${
|
|
562
|
+
{history.slice(0, 5).map((h) => (
|
|
563
|
+
<li key={`${h.at}-${h.filename}`}>
|
|
552
564
|
<button
|
|
553
565
|
type="button"
|
|
554
566
|
onClick={() => {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// from the WS layer and from a future server-driven auto-snapshot hook.
|
|
6
6
|
|
|
7
7
|
import type { Dirent } from 'node:fs';
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
8
9
|
import { readdir } from 'node:fs/promises';
|
|
9
10
|
import path from 'node:path';
|
|
10
11
|
|
|
@@ -37,10 +38,14 @@ function fileSlug(file: string, designRel: string): string {
|
|
|
37
38
|
}
|
|
38
39
|
const prefix = `${designRel.replace(/^\/+|\/+$/g, '')}/`;
|
|
39
40
|
if (p.startsWith(prefix)) p = p.slice(prefix.length);
|
|
41
|
+
// Extension strip mirrors bin/slug.sh (the canonical `_history/<slug>/`
|
|
42
|
+
// recipe) — previously only `.html` was stripped, so `.tsx` canvases got a
|
|
43
|
+
// `ui-foo.tsx` history dir that /design:rollback (slug.sh: `ui-foo`) could
|
|
44
|
+
// never find. DDR-102 conflict snapshots rely on the dirs matching.
|
|
40
45
|
return p
|
|
41
46
|
.replace(/\//g, '-')
|
|
42
47
|
.replace(/\s+/g, '_')
|
|
43
|
-
.replace(/\.html$/i, '')
|
|
48
|
+
.replace(/\.(tsx|jsx|html?|css|json|md)$/i, '')
|
|
44
49
|
.replace(/^\.+/, '')
|
|
45
50
|
.toLowerCase();
|
|
46
51
|
}
|
|
@@ -60,8 +65,20 @@ export function createHistory(ctx: Context): History {
|
|
|
60
65
|
|
|
61
66
|
async function writeSnapshot(file: string, contentBytes: Uint8Array | string, reason: string) {
|
|
62
67
|
const slug = fileSlug(file, ctx.paths.designRel);
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
// Snapshot blob keeps the source file's extension (a `.tsx` body snapshot
|
|
69
|
+
// is a `.tsx` blob); unknown/absent extension falls back to `.html`.
|
|
70
|
+
const extMatch = /\.(tsx|jsx|html?|css|json|svg|md)$/i.exec(file);
|
|
71
|
+
const ext = extMatch ? extMatch[0].toLowerCase() : '.html';
|
|
72
|
+
// Two snapshots can land within the same millisecond (the DDR-102 dual
|
|
73
|
+
// pre-sync pair does) — toISOString() would collide and the second blob
|
|
74
|
+
// would overwrite the first. Bump by 1 ms until the slot is free.
|
|
75
|
+
let tsMs = Date.now();
|
|
76
|
+
let ts = new Date(tsMs).toISOString();
|
|
77
|
+
while (existsSync(snapshotPath(slug, ts, ext)) || existsSync(metaPath(slug, ts))) {
|
|
78
|
+
tsMs += 1;
|
|
79
|
+
ts = new Date(tsMs).toISOString();
|
|
80
|
+
}
|
|
81
|
+
const contentPath = snapshotPath(slug, ts, ext);
|
|
65
82
|
const meta: Snapshot = {
|
|
66
83
|
slug,
|
|
67
84
|
ts,
|