@1agh/maude 0.28.1 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/{plugins/design/dev-server → apps/studio}/ai-banner.tsx +1 -1
- package/{plugins/design/dev-server → apps/studio}/annotations-context-toolbar.tsx +285 -5
- package/{plugins/design/dev-server → apps/studio}/annotations-layer.tsx +1206 -119
- package/{plugins/design/dev-server → apps/studio}/api.ts +486 -32
- 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/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 +438 -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 +5 -3
- package/{plugins/design/dev-server → apps/studio}/canvas-arrowheads.ts +1 -1
- 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 +46 -12
- package/{plugins/design/dev-server → apps/studio}/canvas-icons.tsx +60 -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 +56 -41
- package/{plugins/design/dev-server → apps/studio}/client/app.jsx +1713 -530
- package/apps/studio/client/canvas-url.js +81 -0
- package/{plugins/design/dev-server → apps/studio}/client/comments-overlay.css +41 -27
- package/{plugins/design/dev-server → apps/studio}/client/index.html +2 -1
- package/apps/studio/client/styles/1-tokens-maude.css +296 -0
- package/apps/studio/client/styles/3-shell-maude.css +459 -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 +357 -34
- package/apps/studio/client/styles/5-maude-overrides.css +123 -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 +191 -0
- package/apps/studio/client/tour/usage-tour.js +35 -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 +1 -1
- 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 +1 -1
- package/{plugins/design/dev-server → apps/studio}/comments-overlay.tsx +25 -6
- package/{plugins/design/dev-server → apps/studio}/context-menu.tsx +1 -1
- package/{plugins/design/dev-server → apps/studio}/contextual-toolbar.tsx +4 -7
- package/{plugins/design/dev-server → apps/studio}/cursors-overlay.tsx +1 -1
- package/apps/studio/dist/client.bundle.js +22825 -0
- package/apps/studio/dist/comment-mount.js +1960 -0
- package/apps/studio/dist/styles.css +5695 -0
- package/{plugins/design/dev-server → apps/studio}/dom-selection.ts +75 -4
- 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 +13 -1
- package/{plugins/design/dev-server → apps/studio}/http.ts +102 -1
- package/{plugins/design/dev-server → apps/studio}/input-router.tsx +8 -1
- package/{plugins/design/dev-server → apps/studio}/inspect.ts +20 -3
- package/{plugins/design/dev-server → apps/studio}/marquee-overlay.tsx +5 -2
- package/{plugins/design/dev-server → apps/studio}/participants-chrome.tsx +1 -1
- package/{plugins/design/dev-server → apps/studio}/paths.ts +15 -11
- package/{plugins/design/dev-server → apps/studio}/server.ts +18 -0
- package/{plugins/design/dev-server → apps/studio}/sync/index.ts +1 -1
- 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-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/{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 +17 -1
- package/apps/studio/test/canvas-url.test.ts +99 -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/tour-overlay.test.tsx +56 -0
- package/{plugins/design/dev-server → apps/studio}/test/use-annotation-resize.test.ts +43 -0
- package/{plugins/design/dev-server → apps/studio}/test/use-tool-mode.test.tsx +4 -1
- 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 +4 -69
- package/{plugins/design/dev-server → apps/studio}/undo-hud.tsx +1 -1
- package/{plugins/design/dev-server → apps/studio}/undo-stack.ts +1 -1
- package/{plugins/design/dev-server → apps/studio}/use-agent-presence.tsx +1 -1
- package/{plugins/design/dev-server → apps/studio}/use-annotation-resize.tsx +40 -7
- package/{plugins/design/dev-server → apps/studio}/use-annotation-selection.tsx +1 -1
- 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 +1 -1
- package/apps/studio/use-canvas-media-drop.tsx +343 -0
- package/{plugins/design/dev-server → apps/studio}/use-collab.tsx +1 -1
- 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 +9 -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 +3 -1
- package/{plugins/design/dev-server → apps/studio}/use-undo-stack.tsx +1 -1
- package/apps/studio/whats-new.json +139 -0
- package/apps/studio/whats-new.schema.json +70 -0
- package/apps/studio/whats-new.ts +87 -0
- package/cli/bin/maude.mjs +10 -0
- package/cli/commands/design.mjs +15 -7
- package/cli/commands/design.test.mjs +36 -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/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/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/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 → 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/_png-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-edit.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}/history.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/agent.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/sync/atomic-write.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/sync/codec.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/sync/connection-state.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/migrate-seed.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/sync/origins.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/sync/projection.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/sync/status.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/activity.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-edit.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-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-room.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/csp-canvas-shell.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-migrate.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-agent.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-codec.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/sync-connection-state.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-hardening.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/sync-hubs-config.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-runtime.test.ts +0 -0
- /package/{plugins/design/dev-server → apps/studio}/test/sync-status.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-artboard-drag.test.ts +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-collab.test.ts +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,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file draw/morph.ts — deterministic shape-morph producer for the IR
|
|
3
|
+
* @scope apps/studio/draw/morph.ts
|
|
4
|
+
* @purpose Turn ONE base `d` path into an animatable `d`-morph {@link Track}
|
|
5
|
+
* of VERTEX ARRAYS — the cross-renderer interpolable form. Every
|
|
6
|
+
* keyframe shares an identical command template + a fixed vertex
|
|
7
|
+
* count, which is the hard constraint that lets web (SMIL/motion)
|
|
8
|
+
* AND Lottie tween the morph without reparsing geometry per frame.
|
|
9
|
+
*
|
|
10
|
+
* This is the engine replacement for the studyfi-v3 "Python
|
|
11
|
+
* perturbation outside the engine" anti-pattern (DDR-094 Findings):
|
|
12
|
+
* `morphVariants(base, { n, jitter, seed })` is PURE + deterministic
|
|
13
|
+
* (a seeded LCG, no `Math.random`), so a morph is reproducible and
|
|
14
|
+
* reviewable as code.
|
|
15
|
+
*
|
|
16
|
+
* Constraint: morph paths must use ABSOLUTE, pair-coordinate
|
|
17
|
+
* commands only (`M L C S Q T Z`). `H`/`V`/`A` and relative commands
|
|
18
|
+
* throw — they break the "fixed vertex count" interpolability
|
|
19
|
+
* invariant. The engine's `blobPath` (M…C…Z) already conforms.
|
|
20
|
+
*
|
|
21
|
+
* React-free (DDR-067); depends only on `animate.ts` + the `Point`
|
|
22
|
+
* type.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { CubicBezierHandles, Keyframe, Track } from './animate.ts';
|
|
26
|
+
import type { Point } from './primitives.ts';
|
|
27
|
+
|
|
28
|
+
/** One command of a parsed morph path + how many (x,y) pairs it consumes. */
|
|
29
|
+
export interface MorphCommand {
|
|
30
|
+
cmd: 'M' | 'L' | 'C' | 'S' | 'Q' | 'T' | 'Z';
|
|
31
|
+
/** Number of (x,y) vertex pairs this command consumes (`Z` = 0). */
|
|
32
|
+
nPairs: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A shared path template: the command skeleton every morph keyframe reuses. */
|
|
36
|
+
export interface PathTemplate {
|
|
37
|
+
commands: MorphCommand[];
|
|
38
|
+
/** Total vertex count = Σ nPairs. The fixed-length morph invariant. */
|
|
39
|
+
vertexCount: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A parsed morph path: the reusable template + its base vertex array. */
|
|
43
|
+
export interface ParsedMorph {
|
|
44
|
+
template: PathTemplate;
|
|
45
|
+
vertices: Point[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const PAIRS_PER_CMD: Record<MorphCommand['cmd'], number> = {
|
|
49
|
+
M: 1,
|
|
50
|
+
L: 1,
|
|
51
|
+
C: 3,
|
|
52
|
+
S: 2,
|
|
53
|
+
Q: 2,
|
|
54
|
+
T: 1,
|
|
55
|
+
Z: 0,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const NUM_RE = /-?\d*\.?\d+(?:[eE][-+]?\d+)?/g;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Parse an absolute, pair-coordinate `d` path into a {@link PathTemplate} plus
|
|
62
|
+
* its flat vertex array (vertices in document order). Throws on relative or
|
|
63
|
+
* `H`/`V`/`A` commands — they violate the fixed-vertex-count morph constraint.
|
|
64
|
+
*/
|
|
65
|
+
export function parseMorphPath(d: string): ParsedMorph {
|
|
66
|
+
const commands: MorphCommand[] = [];
|
|
67
|
+
const vertices: Point[] = [];
|
|
68
|
+
// Split on PATH-command letters only — NOT every letter — so the `e`/`E` of an
|
|
69
|
+
// exponent (`2e1`) stays inside its number rather than being read as a command.
|
|
70
|
+
const re = /([MLHVCSQTAZmlhvcsqtaz])([^MLHVCSQTAZmlhvcsqtaz]*)/g;
|
|
71
|
+
let m: RegExpExecArray | null = re.exec(d);
|
|
72
|
+
for (; m !== null; m = re.exec(d)) {
|
|
73
|
+
const letter = m[1];
|
|
74
|
+
if (letter === 'z' || letter === 'Z') {
|
|
75
|
+
commands.push({ cmd: 'Z', nPairs: 0 });
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (!(letter in PAIRS_PER_CMD)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`parseMorphPath: command "${letter}" is not morph-safe — use only ` +
|
|
81
|
+
'absolute pair-coordinate commands (M L C S Q T Z); the blobPath form conforms'
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const cmd = letter as MorphCommand['cmd'];
|
|
85
|
+
const nPairs = PAIRS_PER_CMD[cmd];
|
|
86
|
+
const nums = (m[2].match(NUM_RE) ?? []).map(Number);
|
|
87
|
+
if (nums.length !== nPairs * 2) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`parseMorphPath: command "${cmd}" expected ${nPairs * 2} numbers, got ${nums.length} ` +
|
|
90
|
+
'(repeated/implicit commands are not supported — emit one explicit command per segment)'
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
commands.push({ cmd, nPairs });
|
|
94
|
+
for (let i = 0; i < nums.length; i += 2) vertices.push({ x: nums[i], y: nums[i + 1] });
|
|
95
|
+
}
|
|
96
|
+
return { template: { commands, vertexCount: vertices.length }, vertices };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Format a number the way the rest of the engine does (no float noise). */
|
|
100
|
+
function fmt(n: number): string {
|
|
101
|
+
return Number.isInteger(n) ? String(n) : String(Math.round(n * 1e4) / 1e4);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Reassemble a `d` string from a template + a vertex array (the inverse of
|
|
106
|
+
* {@link parseMorphPath}). Throws if the vertex count doesn't match the
|
|
107
|
+
* template — the morph invariant, checked at the seam.
|
|
108
|
+
*/
|
|
109
|
+
export function templateToPath(template: PathTemplate, vertices: Point[]): string {
|
|
110
|
+
if (vertices.length !== template.vertexCount) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`templateToPath: vertex count ${vertices.length} ≠ template ${template.vertexCount}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const parts: string[] = [];
|
|
116
|
+
let vi = 0;
|
|
117
|
+
for (const c of template.commands) {
|
|
118
|
+
if (c.cmd === 'Z') {
|
|
119
|
+
parts.push('Z');
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const coords: string[] = [];
|
|
123
|
+
for (let p = 0; p < c.nPairs; p++) {
|
|
124
|
+
const v = vertices[vi++];
|
|
125
|
+
coords.push(`${fmt(v.x)} ${fmt(v.y)}`);
|
|
126
|
+
}
|
|
127
|
+
parts.push(`${c.cmd}${coords.join(' ')}`);
|
|
128
|
+
}
|
|
129
|
+
return parts.join('');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Deterministic LCG in [0,1) — same generator the geometry layer uses. */
|
|
133
|
+
function lcg(seed: number): () => number {
|
|
134
|
+
let s = seed >>> 0 || 1;
|
|
135
|
+
return () => {
|
|
136
|
+
s = (s * 1664525 + 1013904223) >>> 0;
|
|
137
|
+
return s / 4294967296;
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface MorphVariantsOpts {
|
|
142
|
+
/** Number of distinct jittered variants between the base endpoints (default 3). */
|
|
143
|
+
n?: number;
|
|
144
|
+
/** Max per-vertex displacement in user units (default 2). 0 ⇒ no motion. */
|
|
145
|
+
jitter?: number;
|
|
146
|
+
/** Integer seed for the deterministic jitter (default 1). */
|
|
147
|
+
seed?: number;
|
|
148
|
+
/** Total morph duration in seconds (default 1). */
|
|
149
|
+
dur?: number;
|
|
150
|
+
/** Per-segment easing (default linear). */
|
|
151
|
+
ease?: CubicBezierHandles;
|
|
152
|
+
/** Node id this track drives. */
|
|
153
|
+
target?: string;
|
|
154
|
+
/**
|
|
155
|
+
* Close the loop by returning to the base shape on the final keyframe
|
|
156
|
+
* (default true) — a seamless `repeat` morph.
|
|
157
|
+
*/
|
|
158
|
+
loop?: boolean;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The morph producer's output: the IR track + the template to render it. */
|
|
162
|
+
export interface MorphResult {
|
|
163
|
+
track: Track<Point[]>;
|
|
164
|
+
template: PathTemplate;
|
|
165
|
+
/** Render any keyframe's vertex array back to a `d` string. */
|
|
166
|
+
toPath: (vertices: Point[]) => string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Produce a deterministic `d`-morph track from a base path. Every keyframe is a
|
|
171
|
+
* vertex array of the SAME length (the base's), jittered by a seeded LCG, evenly
|
|
172
|
+
* spaced over `dur`. With `loop` (default) the last keyframe is the base again,
|
|
173
|
+
* so a repeating timeline morphs seamlessly. Pure + reproducible — the engine
|
|
174
|
+
* replacement for ad-hoc out-of-engine perturbation.
|
|
175
|
+
*/
|
|
176
|
+
export function morphVariants(basePath: string, opts: MorphVariantsOpts = {}): MorphResult {
|
|
177
|
+
const n = Math.max(1, opts.n ?? 3);
|
|
178
|
+
const jitter = opts.jitter ?? 2;
|
|
179
|
+
const dur = opts.dur ?? 1;
|
|
180
|
+
const loop = opts.loop ?? true;
|
|
181
|
+
const { template, vertices: base } = parseMorphPath(basePath);
|
|
182
|
+
const rnd = lcg(opts.seed ?? 1);
|
|
183
|
+
|
|
184
|
+
// Keyframe times: base at 0, n jittered variants, optional base again at dur.
|
|
185
|
+
const stops = loop ? n + 2 : n + 1; // includes the base endpoint(s)
|
|
186
|
+
const keyframes: Keyframe<Point[]>[] = [];
|
|
187
|
+
for (let k = 0; k < stops; k++) {
|
|
188
|
+
const t = (k / (stops - 1)) * dur;
|
|
189
|
+
const isBase = k === 0 || (loop && k === stops - 1);
|
|
190
|
+
const value: Point[] = isBase
|
|
191
|
+
? base.map((p) => ({ ...p }))
|
|
192
|
+
: base.map((p) => ({
|
|
193
|
+
x: p.x + (rnd() * 2 - 1) * jitter,
|
|
194
|
+
y: p.y + (rnd() * 2 - 1) * jitter,
|
|
195
|
+
}));
|
|
196
|
+
const kf: Keyframe<Point[]> = { t, value };
|
|
197
|
+
if (opts.ease && k < stops - 1) kf.ease = opts.ease;
|
|
198
|
+
keyframes.push(kf);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const track: Track<Point[]> = { property: 'd', keyframes };
|
|
202
|
+
if (opts.target !== undefined) track.target = opts.target;
|
|
203
|
+
return { track, template, toPath: (v) => templateToPath(template, v) };
|
|
204
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file draw/optimize.ts — Phase 25 geometry-engine SVGO wrapper
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/draw/optimize.ts
|
|
4
4
|
* @purpose The single external dependency (SVGO — DDR-071). Two jobs:
|
|
5
5
|
* 1. Minify the final on-disk SVG asset (multipass, 2-decimal
|
|
6
6
|
* precision) without stripping the a11y/scaling contract.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file draw/palette.ts — Phase 25 geometry-engine color layer
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/draw/palette.ts
|
|
4
4
|
* @purpose Color correctness the draw-agent verifies from SOURCE, never from
|
|
5
5
|
* the vision model (VLMs misread color confidently):
|
|
6
6
|
* • WCAG 2.1 relative-luminance + contrast ratio (4.5 / 3 / 7);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file draw/primitives.ts — Phase 25 geometry-engine primitive layer
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/draw/primitives.ts
|
|
4
4
|
* @purpose The `DrawPrimitive` union + grid-snapped constructors + transform
|
|
5
5
|
* composition + viewBox presets. This is the "intent" layer the
|
|
6
6
|
* draw-agent emits: the LLM names shapes and where they go; the
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file draw/serialize-animate.ts — IR Timeline → animated SVG/JSX
|
|
3
|
+
* @scope apps/studio/draw/serialize-animate.ts
|
|
4
|
+
* @purpose Time-generalize the DDR-067 single-source invariant: ONE node tree
|
|
5
|
+
* + ONE {@link Timeline} emits BOTH an animated SVG (on-disk `.svg`
|
|
6
|
+
* form) AND an animated JSX string (inline canvas-preview form). Both
|
|
7
|
+
* render the SAME tree — `primitivesToNodes` from `serialize.ts` with
|
|
8
|
+
* SMIL animation children injected onto each track's target — so the
|
|
9
|
+
* two forms can never structurally drift (the test asserts this).
|
|
10
|
+
*
|
|
11
|
+
* Mechanism (per the DDR-094 conversion rules + `_draw-motion-rules`):
|
|
12
|
+
* • `d` shape morph → `<animate attributeName="d" calcMode="spline">`
|
|
13
|
+
* (NEVER CSS `d:path()`, which doesn't animate live).
|
|
14
|
+
* • opacity / fill / stroke → `<animate>`.
|
|
15
|
+
* • translate / scale / rotate → `<animateTransform additive="sum">`
|
|
16
|
+
* — the position-vs-animation split: animated transforms compose
|
|
17
|
+
* with (never clobber) a static `transform=` attribute.
|
|
18
|
+
* • Per-segment easing → `keySplines` + `calcMode="spline"`, taken
|
|
19
|
+
* verbatim from the IR `cubic-bezier` handles (overshoot survives).
|
|
20
|
+
*
|
|
21
|
+
* Reduced motion: emitted as a `<style>` `prefers-reduced-motion`
|
|
22
|
+
* gate for the SVG form (CSS-driven host). SMIL itself ignores the
|
|
23
|
+
* CSS catch-all (a re-discovered gotcha — DDR-094 background); the
|
|
24
|
+
* durable RM story is the host wrapper (`useReducedMotion`) for JSX
|
|
25
|
+
* and the Lottie host for production. Documented in `_draw-motion-rules.md`.
|
|
26
|
+
*
|
|
27
|
+
* Production delivery is Lottie (DDR-094) — this is the maude
|
|
28
|
+
* authoring/preview form. React-free (DDR-067): JSX is a STRING.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import type { CubicBezierHandles, Repeat, Timeline, Track, TrackValue } from './animate.ts';
|
|
32
|
+
import { resolveStagger } from './animate.ts';
|
|
33
|
+
import type { DrawPrimitive, Point } from './primitives.ts';
|
|
34
|
+
import {
|
|
35
|
+
type Dialect,
|
|
36
|
+
primitivesToNodes,
|
|
37
|
+
renderNode,
|
|
38
|
+
type SerializeOpts,
|
|
39
|
+
type SvgNode,
|
|
40
|
+
} from './serialize.ts';
|
|
41
|
+
|
|
42
|
+
/** Per-target renderer for a `d`-morph track's vertex arrays → a path string. */
|
|
43
|
+
export type PathRenderer = (vertices: Point[]) => string;
|
|
44
|
+
|
|
45
|
+
export interface AnimateOpts extends SerializeOpts {
|
|
46
|
+
/**
|
|
47
|
+
* Per-target path renderers for `d`-morph tracks (a {@link Track} on `d`
|
|
48
|
+
* carries vertex arrays; rendering them to `d` strings needs the shared
|
|
49
|
+
* command template). Wire `{ [targetId]: morphResult.toPath }`. A `d` track
|
|
50
|
+
* whose target has no renderer throws.
|
|
51
|
+
*/
|
|
52
|
+
pathRenderers?: Record<string, PathRenderer>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
56
|
+
// Animation plan — the dialect-neutral intermediate both forms share
|
|
57
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/** Properties driven through `<animateTransform>` rather than `<animate>`. */
|
|
60
|
+
const TRANSFORM_TYPES: Record<string, 'translate' | 'scale' | 'rotate'> = {
|
|
61
|
+
translate: 'translate',
|
|
62
|
+
scale: 'scale',
|
|
63
|
+
rotate: 'rotate',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Logical property → SMIL `attributeName` (transforms handled separately). */
|
|
67
|
+
const ATTR_NAME: Record<string, string> = {
|
|
68
|
+
d: 'd',
|
|
69
|
+
opacity: 'opacity',
|
|
70
|
+
fillOpacity: 'fill-opacity',
|
|
71
|
+
strokeOpacity: 'stroke-opacity',
|
|
72
|
+
fill: 'fill',
|
|
73
|
+
stroke: 'stroke',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** One resolved, normalized animation (a single SMIL element's worth). */
|
|
77
|
+
export interface AnimEntry {
|
|
78
|
+
target: string;
|
|
79
|
+
property: string;
|
|
80
|
+
/** SMIL element to emit. */
|
|
81
|
+
kind: 'animate' | 'animateTransform';
|
|
82
|
+
/** `attributeName` (animate) or transform `type` (animateTransform). */
|
|
83
|
+
attr: string;
|
|
84
|
+
/** Serialized keyframe values (`;`-joined at render). */
|
|
85
|
+
values: string[];
|
|
86
|
+
/** Normalized 0–1 keyframe times (first 0, last 1). */
|
|
87
|
+
keyTimes: number[];
|
|
88
|
+
/** `n-1` `"x1 y1 x2 y2"` splines when any segment eases; else undefined. */
|
|
89
|
+
keySplines?: string[];
|
|
90
|
+
/** Animation window start (seconds). */
|
|
91
|
+
begin: number;
|
|
92
|
+
/** Animation window duration (seconds). */
|
|
93
|
+
dur: number;
|
|
94
|
+
repeat: Repeat;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function num(n: number): string {
|
|
98
|
+
return Number.isInteger(n) ? String(n) : String(Math.round(n * 1e4) / 1e4);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function serializeValue(
|
|
102
|
+
property: string,
|
|
103
|
+
v: TrackValue,
|
|
104
|
+
pathRenderers: Record<string, PathRenderer>,
|
|
105
|
+
target: string
|
|
106
|
+
): string {
|
|
107
|
+
if (property === 'd') {
|
|
108
|
+
const r = pathRenderers[target];
|
|
109
|
+
if (!r) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`serialize-animate: d-morph track on "${target}" needs a pathRenderer ` +
|
|
112
|
+
'(pass { pathRenderers: { [target]: morphResult.toPath } })'
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return r(v as Point[]);
|
|
116
|
+
}
|
|
117
|
+
if (typeof v === 'string') return v;
|
|
118
|
+
if (typeof v === 'number') return num(v);
|
|
119
|
+
if (Array.isArray(v)) {
|
|
120
|
+
// number[] (transform components / stops). Point[] only valid for `d` (above).
|
|
121
|
+
return (v as number[]).map(num).join(' ');
|
|
122
|
+
}
|
|
123
|
+
throw new Error(`serialize-animate: unserializable value for "${property}"`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function splineStr(h: CubicBezierHandles): string {
|
|
127
|
+
return h.map(num).join(' ');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Resolve one track into an {@link AnimEntry} (normalize times, splines). */
|
|
131
|
+
function planTrack(
|
|
132
|
+
tr: Track,
|
|
133
|
+
pathRenderers: Record<string, PathRenderer>,
|
|
134
|
+
repeat: Repeat
|
|
135
|
+
): AnimEntry | null {
|
|
136
|
+
const target = tr.target;
|
|
137
|
+
if (!target) return null; // untargeted tracks can't be serialized
|
|
138
|
+
const kfs = tr.keyframes;
|
|
139
|
+
if (kfs.length < 2) return null;
|
|
140
|
+
|
|
141
|
+
const begin = kfs[0].t;
|
|
142
|
+
const dur = kfs[kfs.length - 1].t - begin;
|
|
143
|
+
if (dur <= 0) return null;
|
|
144
|
+
|
|
145
|
+
const keyTimes = kfs.map((k) => Math.min(1, Math.max(0, (k.t - begin) / dur)));
|
|
146
|
+
const values = kfs.map((k) => serializeValue(tr.property, k.value, pathRenderers, target));
|
|
147
|
+
|
|
148
|
+
// keySplines: one per interval. Present only if at least one segment eases.
|
|
149
|
+
const hasEase = kfs.slice(0, -1).some((k) => k.ease);
|
|
150
|
+
let keySplines: string[] | undefined;
|
|
151
|
+
if (hasEase) {
|
|
152
|
+
keySplines = kfs.slice(0, -1).map((k) => splineStr(k.ease ?? [0, 0, 1, 1]));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const transformType = TRANSFORM_TYPES[tr.property];
|
|
156
|
+
const entry: AnimEntry = transformType
|
|
157
|
+
? {
|
|
158
|
+
target,
|
|
159
|
+
property: tr.property,
|
|
160
|
+
kind: 'animateTransform',
|
|
161
|
+
attr: transformType,
|
|
162
|
+
values,
|
|
163
|
+
keyTimes,
|
|
164
|
+
begin,
|
|
165
|
+
dur,
|
|
166
|
+
repeat,
|
|
167
|
+
}
|
|
168
|
+
: {
|
|
169
|
+
target,
|
|
170
|
+
property: tr.property,
|
|
171
|
+
kind: 'animate',
|
|
172
|
+
attr: ATTR_NAME[tr.property] ?? tr.property,
|
|
173
|
+
values,
|
|
174
|
+
keyTimes,
|
|
175
|
+
begin,
|
|
176
|
+
dur,
|
|
177
|
+
repeat,
|
|
178
|
+
};
|
|
179
|
+
if (keySplines) entry.keySplines = keySplines;
|
|
180
|
+
return entry;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Build the dialect-neutral animation plan from a timeline. Resolves `stagger`,
|
|
185
|
+
* normalizes each track to its own [0,1] window, and maps property → SMIL kind.
|
|
186
|
+
* Both serializers consume this — it IS the single source the parity test checks.
|
|
187
|
+
*/
|
|
188
|
+
export function buildAnimPlan(
|
|
189
|
+
timeline: Timeline,
|
|
190
|
+
opts: { pathRenderers?: Record<string, PathRenderer> } = {}
|
|
191
|
+
): AnimEntry[] {
|
|
192
|
+
const tl = resolveStagger(timeline);
|
|
193
|
+
const repeat: Repeat = tl.repeat ?? 'indefinite';
|
|
194
|
+
const renderers = opts.pathRenderers ?? {};
|
|
195
|
+
const out: AnimEntry[] = [];
|
|
196
|
+
for (const tr of tl.tracks) {
|
|
197
|
+
const e = planTrack(tr, renderers, repeat);
|
|
198
|
+
if (e) out.push(e);
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
204
|
+
// SMIL node injection
|
|
205
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
function repeatCount(r: Repeat): string {
|
|
208
|
+
return r === 'indefinite' ? 'indefinite' : String(r);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** An {@link AnimEntry} → a leaf {@link SvgNode} (`<animate>`/`<animateTransform>`). */
|
|
212
|
+
function entryToNode(e: AnimEntry): SvgNode {
|
|
213
|
+
const attrs: Array<[string, string]> = [];
|
|
214
|
+
if (e.kind === 'animateTransform') {
|
|
215
|
+
attrs.push(['attributeName', 'transform']);
|
|
216
|
+
attrs.push(['type', e.attr]);
|
|
217
|
+
attrs.push(['additive', 'sum']); // compose, never clobber a static transform=
|
|
218
|
+
} else {
|
|
219
|
+
attrs.push(['attributeName', e.attr]);
|
|
220
|
+
}
|
|
221
|
+
attrs.push(['values', e.values.join(';')]);
|
|
222
|
+
attrs.push(['keyTimes', e.keyTimes.map(num).join(';')]);
|
|
223
|
+
if (e.keySplines) {
|
|
224
|
+
attrs.push(['calcMode', 'spline']);
|
|
225
|
+
attrs.push(['keySplines', e.keySplines.join(';')]);
|
|
226
|
+
}
|
|
227
|
+
if (e.begin > 0) attrs.push(['begin', `${num(e.begin)}s`]);
|
|
228
|
+
attrs.push(['dur', `${num(e.dur)}s`]);
|
|
229
|
+
attrs.push(['repeatCount', repeatCount(e.repeat)]);
|
|
230
|
+
if (e.repeat !== 'indefinite') attrs.push(['fill', 'freeze']);
|
|
231
|
+
return { tag: e.kind, attrs, children: [] };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Find a node by `id` (depth-first) and append animation children to it. */
|
|
235
|
+
function injectAnimations(
|
|
236
|
+
root: SvgNode,
|
|
237
|
+
plan: AnimEntry[]
|
|
238
|
+
): { injected: number; missing: string[] } {
|
|
239
|
+
const byTarget = new Map<string, AnimEntry[]>();
|
|
240
|
+
for (const e of plan) {
|
|
241
|
+
const list = byTarget.get(e.target) ?? [];
|
|
242
|
+
list.push(e);
|
|
243
|
+
byTarget.set(e.target, list);
|
|
244
|
+
}
|
|
245
|
+
const found = new Set<string>();
|
|
246
|
+
|
|
247
|
+
const walk = (node: SvgNode): void => {
|
|
248
|
+
const id = node.attrs.find(([k]) => k === 'id')?.[1];
|
|
249
|
+
if (id && byTarget.has(id)) {
|
|
250
|
+
for (const e of byTarget.get(id) as AnimEntry[]) node.children.push(entryToNode(e));
|
|
251
|
+
found.add(id);
|
|
252
|
+
}
|
|
253
|
+
for (const c of node.children) walk(c);
|
|
254
|
+
};
|
|
255
|
+
walk(root);
|
|
256
|
+
|
|
257
|
+
const missing = [...byTarget.keys()].filter((t) => !found.has(t));
|
|
258
|
+
return { injected: found.size, missing };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Reduced-motion `<style>` (CSS-host best-effort; SMIL caveat documented). */
|
|
262
|
+
function reducedMotionStyle(): SvgNode {
|
|
263
|
+
const css =
|
|
264
|
+
'@media (prefers-reduced-motion: reduce){' +
|
|
265
|
+
'*{animation-duration:.001ms!important;animation-iteration-count:1!important;' +
|
|
266
|
+
'transition-duration:.001ms!important}}';
|
|
267
|
+
return { tag: 'style', attrs: [], children: [], text: css };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
271
|
+
// Public serializers
|
|
272
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
273
|
+
|
|
274
|
+
function buildAnimatedTree(
|
|
275
|
+
primitives: DrawPrimitive[],
|
|
276
|
+
timeline: Timeline,
|
|
277
|
+
opts: AnimateOpts,
|
|
278
|
+
dialect: Dialect
|
|
279
|
+
): { tree: SvgNode; missing: string[] } {
|
|
280
|
+
const tree = primitivesToNodes(primitives, opts);
|
|
281
|
+
const plan = buildAnimPlan(timeline, opts);
|
|
282
|
+
const { missing } = injectAnimations(tree, plan);
|
|
283
|
+
// Only the SVG form gets a live <style> RM gate — a CSS `{}` block can't be a
|
|
284
|
+
// JSX text child without entity-escaping the braces; JSX hosts gate via
|
|
285
|
+
// useReducedMotion (see _draw-motion-rules.md).
|
|
286
|
+
if (dialect === 'svg') tree.children.unshift(reducedMotionStyle());
|
|
287
|
+
return { tree, missing };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Serialize primitives + a timeline to an animated SVG document string (SMIL +
|
|
292
|
+
* a `prefers-reduced-motion` `<style>` gate). Throws if a track targets an id
|
|
293
|
+
* not present in the tree (fail loud — a typo'd target silently animates nothing).
|
|
294
|
+
*/
|
|
295
|
+
export function toAnimatedSvg(
|
|
296
|
+
primitives: DrawPrimitive[],
|
|
297
|
+
timeline: Timeline,
|
|
298
|
+
opts: AnimateOpts
|
|
299
|
+
): string {
|
|
300
|
+
const { tree, missing } = buildAnimatedTree(primitives, timeline, opts, 'svg');
|
|
301
|
+
if (missing.length) {
|
|
302
|
+
throw new Error(`toAnimatedSvg: track target(s) not found in tree: ${missing.join(', ')}`);
|
|
303
|
+
}
|
|
304
|
+
return renderNode(tree, 'svg', '');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Serialize primitives + a timeline to an animated JSX string for inline canvas
|
|
309
|
+
* embedding. Renders the SAME SMIL node tree in the JSX dialect (React renders
|
|
310
|
+
* SMIL inside an `<svg>`), so the on-disk and on-canvas animated forms are
|
|
311
|
+
* structurally identical. The reduced-motion gate is the host's responsibility
|
|
312
|
+
* (`useReducedMotion`) — see `_draw-motion-rules.md`.
|
|
313
|
+
*/
|
|
314
|
+
export function toAnimatedJsx(
|
|
315
|
+
primitives: DrawPrimitive[],
|
|
316
|
+
timeline: Timeline,
|
|
317
|
+
opts: AnimateOpts
|
|
318
|
+
): string {
|
|
319
|
+
const { tree, missing } = buildAnimatedTree(primitives, timeline, opts, 'jsx');
|
|
320
|
+
if (missing.length) {
|
|
321
|
+
throw new Error(`toAnimatedJsx: track target(s) not found in tree: ${missing.join(', ')}`);
|
|
322
|
+
}
|
|
323
|
+
return renderNode(tree, 'jsx', '');
|
|
324
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file draw/serialize.ts — Phase 25 geometry-engine serializer
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/draw/serialize.ts
|
|
4
4
|
* @purpose The dual serializer: ONE `DrawPrimitive[]` produces BOTH an
|
|
5
5
|
* optimized SVG string (`toSvg`) for on-disk assets AND a JSX string
|
|
6
6
|
* (`toJsx`) for inline canvas embedding. Both render the SAME
|
|
@@ -379,7 +379,7 @@ export function primitivesToNodes(primitives: DrawPrimitive[], opts: SerializeOp
|
|
|
379
379
|
// Rendering
|
|
380
380
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
381
381
|
|
|
382
|
-
type Dialect = 'svg' | 'jsx';
|
|
382
|
+
export type Dialect = 'svg' | 'jsx';
|
|
383
383
|
|
|
384
384
|
function attrName(key: string, dialect: Dialect): string {
|
|
385
385
|
const mapped = ATTR_DIALECT[key];
|
|
@@ -397,7 +397,13 @@ function escapeAttr(s: string): string {
|
|
|
397
397
|
return s.replace(/&/g, '&').replace(/"/g, '"');
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
|
|
400
|
+
/**
|
|
401
|
+
* Render one {@link SvgNode} tree to a dialect string. Exported so the animation
|
|
402
|
+
* serializer (`serialize-animate.ts`) can render the SAME node tree after it
|
|
403
|
+
* injects SMIL animation children — preserving the DDR-067 single-source
|
|
404
|
+
* invariant for animated marks.
|
|
405
|
+
*/
|
|
406
|
+
export function renderNode(node: SvgNode, dialect: Dialect, indent: string): string {
|
|
401
407
|
const pad = indent;
|
|
402
408
|
const attrParts: string[] = [];
|
|
403
409
|
|