@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file use-annotation-selection.tsx — Phase 5.1 annotation selection store
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/use-annotation-selection.tsx
|
|
4
4
|
* @purpose Parallel to `use-selection-set`, but holds annotation stroke IDs
|
|
5
5
|
* instead of DOM-element selections. Annotation IDs are local
|
|
6
6
|
* identifiers (`s_*`) generated by `rid()` — they never live in
|
|
@@ -10,7 +10,15 @@
|
|
|
10
10
|
* selection resets (the strokes themselves persist via `.annotations.svg`).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
createContext,
|
|
15
|
+
type ReactNode,
|
|
16
|
+
useCallback,
|
|
17
|
+
useContext,
|
|
18
|
+
useMemo,
|
|
19
|
+
useRef,
|
|
20
|
+
useState,
|
|
21
|
+
} from 'react';
|
|
14
22
|
|
|
15
23
|
export interface AnnotationSelectionValue {
|
|
16
24
|
selectedIds: string[];
|
|
@@ -57,10 +65,14 @@ export function AnnotationSelectionProvider({ children }: { children: ReactNode
|
|
|
57
65
|
|
|
58
66
|
// Read live from state at call time so multi-step flows in the same tick
|
|
59
67
|
// (e.g. a router callback that adds then asks `contains`) see the staged
|
|
60
|
-
// result instead of a stale closure capture.
|
|
61
|
-
|
|
68
|
+
// result instead of a stale closure capture. MUST be a real useRef — the
|
|
69
|
+
// pre-FigJam-v3 code created a fresh `{ current }` object literal every
|
|
70
|
+
// render, so the stable useCallback closure kept reading the FIRST render's
|
|
71
|
+
// (empty) selection forever: `contains()` always returned false, which
|
|
72
|
+
// silently broke hull/multi-drag and click-keeps-selection (user-gate
|
|
73
|
+
// finding, 2026-06-11).
|
|
74
|
+
const containsRef = useRef<string[]>(selectedIds);
|
|
62
75
|
containsRef.current = selectedIds;
|
|
63
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: ref read intentionally
|
|
64
76
|
const contains = useCallback((id: string) => containsRef.current.includes(id), []);
|
|
65
77
|
|
|
66
78
|
const value = useMemo<AnnotationSelectionValue>(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file use-annotations-visibility.tsx — Phase 5.1 visibility store
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/use-annotations-visibility.tsx
|
|
4
4
|
* @purpose Tiny boolean store shared by AnnotationsLayer (render gate),
|
|
5
5
|
* ToolPalette (presentation toggle button), and the menubar
|
|
6
6
|
* bridge (`view-annotations` postMessage handler in
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file use-artboard-drag.tsx — Phase 4.2 artboard drag controller
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/use-artboard-drag.tsx
|
|
4
4
|
* @purpose Owns the pointerdown → pointermove → pointerup state machine
|
|
5
5
|
* for dragging artboards on the infinite canvas. The pure
|
|
6
6
|
* `dragReducer` + `commitFromState` are unit-testable without a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file use-canvas-activity.tsx — Phase 13 / DDR-029 activity context.
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/use-canvas-activity.tsx
|
|
4
4
|
* @purpose Iframe-runtime React context fed by the server's `activity` WS
|
|
5
5
|
* messages. `DCArtboard` reads it to render the "agent works here"
|
|
6
6
|
* overlay on the artboards being edited right now.
|
|
@@ -132,7 +132,7 @@ function readActivitySeed(): ActivityMap {
|
|
|
132
132
|
const out: ActivityMap = {};
|
|
133
133
|
for (const file of Object.keys(seed)) {
|
|
134
134
|
const e = seed[file];
|
|
135
|
-
if (
|
|
135
|
+
if (e?.status !== 'active') continue; // only resurrect active overlays
|
|
136
136
|
out[file] = {
|
|
137
137
|
status: 'active',
|
|
138
138
|
artboardIds: Array.isArray(e.artboardIds) ? e.artboardIds : null,
|
|
@@ -199,7 +199,7 @@ export function CanvasActivityProvider({
|
|
|
199
199
|
setMap((prev) => {
|
|
200
200
|
const entry = prev[detail.file];
|
|
201
201
|
// Only remove if it's still the same idle entry (not re-activated).
|
|
202
|
-
if (
|
|
202
|
+
if (entry?.status !== 'idle' || entry.ts !== ts) return prev;
|
|
203
203
|
const { [detail.file]: _drop, ...rest } = prev;
|
|
204
204
|
return rest;
|
|
205
205
|
});
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file use-canvas-media-drop.tsx — Phase 23 canvas media intake
|
|
3
|
+
* @scope apps/studio/use-canvas-media-drop.tsx
|
|
4
|
+
* @purpose OS-level drag-and-drop + clipboard paste of images and URLs onto
|
|
5
|
+
* the canvas. Routes each gesture to a create callback owned by
|
|
6
|
+
* AnnotationsLayer (which holds the commit/undo sink + screenToWorld):
|
|
7
|
+
* • image file → `onImage(file, world)` → optimistic stroke + upload
|
|
8
|
+
* • http(s) URL → `onLink(url, title, world)` → client-only link chip
|
|
9
|
+
*
|
|
10
|
+
* The classification + URL helpers are PURE + exported so the unit
|
|
11
|
+
* tests cover the dispatch matrix without a real DataTransfer /
|
|
12
|
+
* ClipboardEvent. The hook itself only wires the DOM events and the
|
|
13
|
+
* drag-affordance class.
|
|
14
|
+
*
|
|
15
|
+
* Security: link URLs are gated to http(s) (no javascript:/data:);
|
|
16
|
+
* image bytes go through POST /_api/asset (magic-byte sniff + caps,
|
|
17
|
+
* DDR Task 9). Nothing here trusts the dropped content.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { useEffect } from 'react';
|
|
21
|
+
|
|
22
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
// Pure helpers (exported for unit tests)
|
|
24
|
+
|
|
25
|
+
/** A normalized snapshot of a DataTransfer / ClipboardData payload. */
|
|
26
|
+
export interface MediaPayload {
|
|
27
|
+
files: readonly File[];
|
|
28
|
+
/** text/uri-list (DnD) */
|
|
29
|
+
uriList: string;
|
|
30
|
+
/** text/html (carries the anchor text for a dragged/pasted link) */
|
|
31
|
+
html: string;
|
|
32
|
+
/** text/plain */
|
|
33
|
+
plain: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type MediaIntent =
|
|
37
|
+
| { kind: 'image'; file: File }
|
|
38
|
+
| { kind: 'link'; url: string; title: string };
|
|
39
|
+
|
|
40
|
+
/** True only for an absolute http(s) URL — the one scheme a link chip accepts. */
|
|
41
|
+
export function isHttpUrl(value: string): boolean {
|
|
42
|
+
try {
|
|
43
|
+
const u = new URL(value.trim());
|
|
44
|
+
return u.protocol === 'http:' || u.protocol === 'https:';
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Normalize a single token into a safe http(s) URL, or null. You should NOT have
|
|
52
|
+
* to type a scheme to paste a link:
|
|
53
|
+
* - an explicit scheme is kept ONLY if it's http(s) — `javascript:` / `data:`
|
|
54
|
+
* / `file:` / `mailto:` all return null (never reach window.open);
|
|
55
|
+
* - a scheme-less but domain-shaped token (`example.com`, `sub.example.io/x`)
|
|
56
|
+
* gets `https://` prepended. The TLD must be alphabetic (≥2) so a bare
|
|
57
|
+
* decimal like `3.5` or `v1.2` is NOT mistaken for a domain.
|
|
58
|
+
*/
|
|
59
|
+
export function normalizeUrl(raw: string): string | null {
|
|
60
|
+
const s = raw.trim();
|
|
61
|
+
if (!s) return null;
|
|
62
|
+
// Distinguish a real scheme (`http:`, `javascript:`) from a `host:port` colon.
|
|
63
|
+
// A scheme: the part before the FIRST colon is a valid scheme name (no `/`) AND
|
|
64
|
+
// the char after the colon is NOT a digit (a digit there is a port → scheme-less).
|
|
65
|
+
const colon = s.indexOf(':');
|
|
66
|
+
const hasScheme =
|
|
67
|
+
colon > 0 &&
|
|
68
|
+
/^[a-zA-Z][a-zA-Z0-9+.-]*$/.test(s.slice(0, colon)) &&
|
|
69
|
+
!/^\d/.test(s.slice(colon + 1));
|
|
70
|
+
if (hasScheme) return isHttpUrl(s) ? s : null;
|
|
71
|
+
// Scheme-less but domain-shaped (alphabetic TLD ≥2) → assume https.
|
|
72
|
+
if (/^([a-z0-9-]+\.)+[a-z]{2,}(:\d+)?(\/\S*)?$/i.test(s)) {
|
|
73
|
+
const u = `https://${s}`;
|
|
74
|
+
return isHttpUrl(u) ? u : null;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* First link URL in a blob of text, normalized to http(s). Handles a uri-list
|
|
81
|
+
* (newline-separated, `#` comment lines), a bare-domain paste, and an explicit
|
|
82
|
+
* inline URL inside prose. Returns null when none.
|
|
83
|
+
*/
|
|
84
|
+
export function firstHttpUrl(text: string): string | null {
|
|
85
|
+
if (!text) return null;
|
|
86
|
+
for (const raw of text.split(/[\r\n]+/)) {
|
|
87
|
+
const line = raw.trim();
|
|
88
|
+
if (!line || line.startsWith('#')) continue;
|
|
89
|
+
const u = normalizeUrl(line);
|
|
90
|
+
if (u) return u;
|
|
91
|
+
}
|
|
92
|
+
// Fall back to an explicit inline URL token inside prose (a scheme is required
|
|
93
|
+
// here — bare-domain detection only fires on a whole standalone line).
|
|
94
|
+
const m = text.match(/https?:\/\/[^\s"'<>]+/i);
|
|
95
|
+
return m ? normalizeUrl(m[0]) : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Hostname without a leading `www.` — the chip's domain label. */
|
|
99
|
+
export function linkDomain(url: string): string {
|
|
100
|
+
try {
|
|
101
|
+
return new URL(url).hostname.replace(/^www\./, '');
|
|
102
|
+
} catch {
|
|
103
|
+
return url;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** A readable title fallback when no anchor text is available. */
|
|
108
|
+
export function prettifyUrl(url: string): string {
|
|
109
|
+
try {
|
|
110
|
+
const u = new URL(url);
|
|
111
|
+
const host = u.hostname.replace(/^www\./, '');
|
|
112
|
+
const path = u.pathname.replace(/\/+$/, '');
|
|
113
|
+
return path && path !== '/' ? `${host}${path}` : host;
|
|
114
|
+
} catch {
|
|
115
|
+
return url;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Anchor text from a `text/html` DnD/clipboard payload, else null. */
|
|
120
|
+
export function anchorTextFromHtml(html: string): string | null {
|
|
121
|
+
if (!html) return null;
|
|
122
|
+
const m = html.match(/<a\b[^>]*>([\s\S]*?)<\/a>/i);
|
|
123
|
+
if (!m?.[1]) return null;
|
|
124
|
+
const text = m[1]
|
|
125
|
+
.replace(/<[^>]*>/g, '')
|
|
126
|
+
.replace(/\s+/g, ' ')
|
|
127
|
+
.trim();
|
|
128
|
+
return text || null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Decide what a drop / paste is: an image file wins over a URL (a dragged image
|
|
133
|
+
* carries both an image file AND a uri-list pointing at it). Returns null for a
|
|
134
|
+
* plain-text / non-media payload so the caller leaves the event untouched.
|
|
135
|
+
*/
|
|
136
|
+
export function classifyMediaPayload(p: MediaPayload): MediaIntent | null {
|
|
137
|
+
const imageFile = p.files.find((f) => typeof f.type === 'string' && f.type.startsWith('image/'));
|
|
138
|
+
if (imageFile) return { kind: 'image', file: imageFile };
|
|
139
|
+
const url = firstHttpUrl(p.uriList) ?? firstHttpUrl(p.plain);
|
|
140
|
+
if (url) {
|
|
141
|
+
const title = anchorTextFromHtml(p.html) ?? prettifyUrl(url);
|
|
142
|
+
return { kind: 'link', url, title };
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
148
|
+
// Upload
|
|
149
|
+
|
|
150
|
+
/** POST raw image bytes to the capped asset route. Returns the assets/ path. */
|
|
151
|
+
export async function uploadAsset(file: Blob): Promise<{ path: string } | { error: string }> {
|
|
152
|
+
try {
|
|
153
|
+
const res = await fetch('/_api/asset', {
|
|
154
|
+
method: 'POST',
|
|
155
|
+
headers: { 'Content-Type': file.type || 'application/octet-stream' },
|
|
156
|
+
body: file,
|
|
157
|
+
});
|
|
158
|
+
if (!res.ok) {
|
|
159
|
+
let msg = `upload failed (${res.status})`;
|
|
160
|
+
try {
|
|
161
|
+
const j = (await res.json()) as { error?: string };
|
|
162
|
+
if (j?.error) msg = j.error;
|
|
163
|
+
} catch {
|
|
164
|
+
/* non-JSON error body */
|
|
165
|
+
}
|
|
166
|
+
return { error: msg };
|
|
167
|
+
}
|
|
168
|
+
const j = (await res.json()) as { path?: string };
|
|
169
|
+
return typeof j?.path === 'string' ? { path: j.path } : { error: 'malformed upload response' };
|
|
170
|
+
} catch (e) {
|
|
171
|
+
return { error: e instanceof Error ? e.message : 'network error' };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
176
|
+
// Transient toast (no React state — self-contained DOM, mirrors ensure*Styles)
|
|
177
|
+
|
|
178
|
+
const TOAST_CSS = `
|
|
179
|
+
.dc-media-toast {
|
|
180
|
+
position: fixed;
|
|
181
|
+
left: 50%;
|
|
182
|
+
bottom: 108px;
|
|
183
|
+
transform: translateX(-50%);
|
|
184
|
+
z-index: 9;
|
|
185
|
+
max-width: 360px;
|
|
186
|
+
padding: 8px 14px;
|
|
187
|
+
border-radius: 8px;
|
|
188
|
+
background: #26262b;
|
|
189
|
+
color: #fff;
|
|
190
|
+
font-family: var(--maude-chrome-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
191
|
+
font-size: 12px;
|
|
192
|
+
box-shadow: 0 8px 28px rgba(0,0,0,0.34);
|
|
193
|
+
opacity: 0;
|
|
194
|
+
transition: opacity 140ms ease;
|
|
195
|
+
pointer-events: none;
|
|
196
|
+
}
|
|
197
|
+
.dc-media-toast[data-show="1"] { opacity: 1; }
|
|
198
|
+
/* Drop affordance — a focus-accent inset frame while a media drag is over the
|
|
199
|
+
canvas, so the surface reads as a drop target. */
|
|
200
|
+
.dc-media-dragover::after {
|
|
201
|
+
content: "";
|
|
202
|
+
position: fixed;
|
|
203
|
+
inset: 6px;
|
|
204
|
+
z-index: 8;
|
|
205
|
+
border: 2px dashed var(--maude-hud-accent, #d63b1f);
|
|
206
|
+
border-radius: 12px;
|
|
207
|
+
pointer-events: none;
|
|
208
|
+
}
|
|
209
|
+
@media (prefers-reduced-motion: reduce) {
|
|
210
|
+
.dc-media-toast { transition: none; }
|
|
211
|
+
}
|
|
212
|
+
`.trim();
|
|
213
|
+
|
|
214
|
+
function ensureMediaStyles(): void {
|
|
215
|
+
if (typeof document === 'undefined') return;
|
|
216
|
+
if (document.getElementById('dc-media-css')) return;
|
|
217
|
+
const s = document.createElement('style');
|
|
218
|
+
s.id = 'dc-media-css';
|
|
219
|
+
s.textContent = TOAST_CSS;
|
|
220
|
+
document.head.appendChild(s);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Show a brief auto-dismissing toast in the canvas (e.g. an upload failure). */
|
|
224
|
+
export function showCanvasToast(message: string): void {
|
|
225
|
+
if (typeof document === 'undefined') return;
|
|
226
|
+
ensureMediaStyles();
|
|
227
|
+
const el = document.createElement('div');
|
|
228
|
+
el.className = 'dc-media-toast';
|
|
229
|
+
el.setAttribute('role', 'status');
|
|
230
|
+
el.textContent = message;
|
|
231
|
+
document.body.appendChild(el);
|
|
232
|
+
requestAnimationFrame(() => el.setAttribute('data-show', '1'));
|
|
233
|
+
setTimeout(() => {
|
|
234
|
+
el.setAttribute('data-show', '0');
|
|
235
|
+
setTimeout(() => el.remove(), 200);
|
|
236
|
+
}, 2600);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
240
|
+
// Hook
|
|
241
|
+
|
|
242
|
+
export interface MediaDropCallbacks {
|
|
243
|
+
onImage: (file: File, world: [number, number]) => void;
|
|
244
|
+
onLink: (url: string, title: string, world: [number, number]) => void;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function useCanvasMediaDrop(opts: {
|
|
248
|
+
enabled: boolean;
|
|
249
|
+
screenToWorld: (cx: number, cy: number) => [number, number];
|
|
250
|
+
callbacks: MediaDropCallbacks;
|
|
251
|
+
}): void {
|
|
252
|
+
const { enabled, screenToWorld, callbacks } = opts;
|
|
253
|
+
useEffect(() => {
|
|
254
|
+
if (!enabled || typeof document === 'undefined') return;
|
|
255
|
+
ensureMediaStyles();
|
|
256
|
+
const body = document.body;
|
|
257
|
+
|
|
258
|
+
const hasMedia = (dt: DataTransfer | null): boolean => {
|
|
259
|
+
if (!dt) return false;
|
|
260
|
+
const types = Array.from(dt.types ?? []);
|
|
261
|
+
return types.includes('Files') || types.includes('text/uri-list');
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const payloadFromTransfer = (dt: DataTransfer): MediaPayload => ({
|
|
265
|
+
files: dt.files ? Array.from(dt.files) : [],
|
|
266
|
+
uriList: safeGet(dt, 'text/uri-list'),
|
|
267
|
+
html: safeGet(dt, 'text/html'),
|
|
268
|
+
plain: safeGet(dt, 'text/plain'),
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const onDragOver = (e: DragEvent) => {
|
|
272
|
+
if (!hasMedia(e.dataTransfer)) return;
|
|
273
|
+
e.preventDefault();
|
|
274
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
|
275
|
+
body.classList.add('dc-media-dragover');
|
|
276
|
+
};
|
|
277
|
+
const onDragLeave = (e: DragEvent) => {
|
|
278
|
+
// Only clear when the pointer actually leaves the window (related target
|
|
279
|
+
// null), not when crossing between child elements.
|
|
280
|
+
if (e.relatedTarget == null) body.classList.remove('dc-media-dragover');
|
|
281
|
+
};
|
|
282
|
+
const onDrop = (e: DragEvent) => {
|
|
283
|
+
body.classList.remove('dc-media-dragover');
|
|
284
|
+
if (!e.dataTransfer) return;
|
|
285
|
+
const intent = classifyMediaPayload(payloadFromTransfer(e.dataTransfer));
|
|
286
|
+
if (!intent) return; // not media — leave the event for other handlers
|
|
287
|
+
e.preventDefault();
|
|
288
|
+
const world = screenToWorld(e.clientX, e.clientY);
|
|
289
|
+
dispatchIntent(intent, world);
|
|
290
|
+
};
|
|
291
|
+
const onPaste = (e: ClipboardEvent) => {
|
|
292
|
+
const cd = e.clipboardData;
|
|
293
|
+
if (!cd) return;
|
|
294
|
+
const files: File[] = [];
|
|
295
|
+
if (cd.files) for (const f of Array.from(cd.files)) files.push(f);
|
|
296
|
+
// Some browsers expose pasted images only via items, not files.
|
|
297
|
+
if (cd.items) {
|
|
298
|
+
for (const it of Array.from(cd.items)) {
|
|
299
|
+
if (it.kind === 'file' && it.type.startsWith('image/')) {
|
|
300
|
+
const f = it.getAsFile();
|
|
301
|
+
if (f && !files.includes(f)) files.push(f);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const intent = classifyMediaPayload({
|
|
306
|
+
files,
|
|
307
|
+
uriList: safeGet(cd, 'text/uri-list'),
|
|
308
|
+
html: safeGet(cd, 'text/html'),
|
|
309
|
+
plain: safeGet(cd, 'text/plain'),
|
|
310
|
+
});
|
|
311
|
+
if (!intent) return; // plain-text paste — let the existing paste flow run
|
|
312
|
+
e.preventDefault();
|
|
313
|
+
// Paste has no pointer position — drop at the visible viewport centre.
|
|
314
|
+
const world = screenToWorld(window.innerWidth / 2, window.innerHeight / 2);
|
|
315
|
+
dispatchIntent(intent, world);
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const dispatchIntent = (intent: MediaIntent, world: [number, number]) => {
|
|
319
|
+
if (intent.kind === 'image') callbacks.onImage(intent.file, world);
|
|
320
|
+
else if (isHttpUrl(intent.url)) callbacks.onLink(intent.url, intent.title, world);
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
document.addEventListener('dragover', onDragOver);
|
|
324
|
+
document.addEventListener('dragleave', onDragLeave);
|
|
325
|
+
document.addEventListener('drop', onDrop);
|
|
326
|
+
document.addEventListener('paste', onPaste);
|
|
327
|
+
return () => {
|
|
328
|
+
document.removeEventListener('dragover', onDragOver);
|
|
329
|
+
document.removeEventListener('dragleave', onDragLeave);
|
|
330
|
+
document.removeEventListener('drop', onDrop);
|
|
331
|
+
document.removeEventListener('paste', onPaste);
|
|
332
|
+
body.classList.remove('dc-media-dragover');
|
|
333
|
+
};
|
|
334
|
+
}, [enabled, screenToWorld, callbacks]);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function safeGet(dt: DataTransfer, type: string): string {
|
|
338
|
+
try {
|
|
339
|
+
return dt.getData(type) ?? '';
|
|
340
|
+
} catch {
|
|
341
|
+
return '';
|
|
342
|
+
}
|
|
343
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file use-collab.tsx — client-side Yjs collab provider for canvas iframes
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/use-collab.tsx
|
|
4
4
|
* @purpose Mounts a single Y.Doc + Awareness per canvas iframe. Opens a
|
|
5
5
|
* WebSocket to `/_ws/collab/:slug`, speaks the y-websocket binary
|
|
6
6
|
* protocol, exposes hooks for the cursor overlay + Task 3 comments
|
|
@@ -39,26 +39,31 @@ const MESSAGE_AWARENESS = 1;
|
|
|
39
39
|
// Color hash — stable per peer identity.
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* djb2 string hash →
|
|
42
|
+
* djb2 string hash → OKLCH color in a curated palette. Determinism per
|
|
43
43
|
* input name is the load-bearing property: every peer hashing "Alice" must
|
|
44
|
-
* land on the SAME color.
|
|
45
|
-
*
|
|
44
|
+
* land on the SAME color.
|
|
45
|
+
*
|
|
46
|
+
* DS contract (colors-presence specimen): the AI agent rides
|
|
47
|
+
* `--presence-agent` (violet-magenta, hue 322) — "a hue no human state uses,
|
|
48
|
+
* so attribution on a shared canvas is unambiguous". Human hues therefore
|
|
49
|
+
* EXCLUDE the agent band (~292–352) AND the accent indigo band (~245–290,
|
|
50
|
+
* reserved for selection/active). L/C match the DS presence tokens
|
|
51
|
+
* (oklch ≈0.74 0.16) so every cursor reads at the same weight on both themes.
|
|
46
52
|
*/
|
|
47
53
|
const COLOR_PALETTE = [
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
'#03a9f4', // light blue
|
|
57
|
-
'#3f51b5', // indigo
|
|
58
|
-
'#673ab7', // deep purple
|
|
59
|
-
'#9c27b0', // purple
|
|
54
|
+
'oklch(0.70 0.17 12)', // rose
|
|
55
|
+
'oklch(0.72 0.16 40)', // coral
|
|
56
|
+
'oklch(0.78 0.15 78)', // amber (presence-away hue)
|
|
57
|
+
'oklch(0.76 0.16 108)', // lime
|
|
58
|
+
'oklch(0.74 0.16 145)', // green (presence-online hue)
|
|
59
|
+
'oklch(0.75 0.14 172)', // teal
|
|
60
|
+
'oklch(0.73 0.13 200)', // cyan
|
|
61
|
+
'oklch(0.72 0.13 238)', // blue (status-info hue)
|
|
60
62
|
] as const;
|
|
61
63
|
|
|
64
|
+
/** The AI agent's exclusive cursor/avatar hue — `--presence-agent`. */
|
|
65
|
+
export const AGENT_COLOR = 'oklch(0.700 0.190 322)';
|
|
66
|
+
|
|
62
67
|
export function colorForName(name: string): string {
|
|
63
68
|
// COLOR_PALETTE is a non-empty const tuple; the explicit `?? '#000'`
|
|
64
69
|
// fallback is unreachable but satisfies `noUncheckedIndexedAccess`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file use-cursor-modifiers.tsx — T28 (Wave 3)
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/use-cursor-modifiers.tsx
|
|
4
4
|
* @purpose Modifier-aware cursor state machine. Tracks Alt / Shift / Meta
|
|
5
5
|
* modifier state globally and reflects it as `data-mod-*`
|
|
6
6
|
* attributes on `.dc-canvas`. CSS rules consume those attrs to
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file use-keyboard-discipline.tsx — T29 (Wave 3)
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/use-keyboard-discipline.tsx
|
|
4
4
|
* @purpose Keyboard nudge + Cmd+A select-all-in-artboard. Bail when focus
|
|
5
5
|
* is inside an input / textarea / contenteditable so typing
|
|
6
6
|
* into the dev-server's own inputs (file tree filter, etc.)
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
import { useEffect } from 'react';
|
|
30
30
|
|
|
31
31
|
import { useArtboardsContext, useDragStateContext } from './canvas-lib.tsx';
|
|
32
|
+
import { scopedCdSelector, selectorIndex } from './dom-selection.ts';
|
|
32
33
|
import { isEditableTarget } from './input-router.tsx';
|
|
33
34
|
import { type Selection, useSelectionSet } from './use-selection-set.tsx';
|
|
34
35
|
|
|
@@ -85,10 +86,12 @@ export function useKeyboardDiscipline(): void {
|
|
|
85
86
|
for (const el of stamped) {
|
|
86
87
|
const cdId = el.getAttribute('data-cd-id');
|
|
87
88
|
if (!cdId) continue;
|
|
89
|
+
const kdSelector = scopedCdSelector(cdId, id);
|
|
88
90
|
hits.push({
|
|
89
91
|
id: cdId,
|
|
90
|
-
selector:
|
|
92
|
+
selector: kdSelector,
|
|
91
93
|
artboardId: id,
|
|
94
|
+
index: selectorIndex(document, kdSelector, el),
|
|
92
95
|
tag: el.tagName.toLowerCase(),
|
|
93
96
|
});
|
|
94
97
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file use-selection-set.tsx — Phase 4.1 multi-selection store
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/use-selection-set.tsx
|
|
4
4
|
* @purpose Multi-element selection state for canvas-shell. The canvas
|
|
5
5
|
* input router calls `replace()` / `add()` / `clear()`;
|
|
6
6
|
* the provider debounces and posts up to the dev-server shell
|
|
@@ -46,6 +46,14 @@ export interface Selection {
|
|
|
46
46
|
selector: string;
|
|
47
47
|
/** Artboard host (`data-dc-screen`) — for scoping multi-edits in future. */
|
|
48
48
|
artboardId?: string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Occurrence index of this element among `querySelectorAll(selector)`.
|
|
51
|
+
* data-cd-id is stamped per SOURCE element, so a component rendered N times
|
|
52
|
+
* (a list row, or a reusable used twice) yields N DOM nodes with the SAME
|
|
53
|
+
* id+artboard selector. The index disambiguates which instance — resolvers
|
|
54
|
+
* use `querySelectorAll(selector)[index]`. Absent/0 → first match.
|
|
55
|
+
*/
|
|
56
|
+
index?: number;
|
|
49
57
|
/** Snapshot fields filled by the router from `resolveHoverTarget`. */
|
|
50
58
|
tag?: string;
|
|
51
59
|
classes?: string;
|
|
@@ -53,6 +61,15 @@ export interface Selection {
|
|
|
53
61
|
dom_path?: string[];
|
|
54
62
|
bounds?: { x: number; y: number; w: number; h: number } | null;
|
|
55
63
|
html?: string;
|
|
64
|
+
/** Phase 12.2 — authored inline-style values (knob pre-fill) + resolved computed (placeholder hint). */
|
|
65
|
+
authored?: Record<string, string>;
|
|
66
|
+
computed?: Record<string, string>;
|
|
67
|
+
/** Phase 12.3 — authored inline-style props OUTSIDE the curated knob set, so the
|
|
68
|
+
* panel can surface a custom CSS property the user added (e.g. `letter-spacing`). */
|
|
69
|
+
customStyles?: Record<string, string>;
|
|
70
|
+
/** Phase 12.3 — custom HTML attributes on the element (data-, aria-, role, title…),
|
|
71
|
+
* so the panel reflects a custom attribute the user added via the escape hatch. */
|
|
72
|
+
attrs?: Record<string, string>;
|
|
56
73
|
}
|
|
57
74
|
|
|
58
75
|
interface SelectionSetValue {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file use-snap-guides.tsx — Phase 4.2 snap math + guide-line shapes
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/use-snap-guides.tsx
|
|
4
4
|
* @purpose Pure function `computeSnap` that returns a snapped (x, y) for a
|
|
5
5
|
* proposed artboard rect plus the guide lines that should render
|
|
6
6
|
* as visual feedback. No React state; the drag controller calls
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file use-tool-mode.tsx — Phase 4.1 tool-mode store
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/use-tool-mode.tsx
|
|
4
4
|
* @purpose Context + hook for the active canvas tool. Wired into
|
|
5
5
|
* DesignCanvas. Phase 5 will
|
|
6
6
|
* register additional tools (pen, circle, arrow, eraser) via
|
|
@@ -52,10 +52,14 @@ export const DEFAULT_TOOLS: readonly ToolDescriptor[] = Object.freeze([
|
|
|
52
52
|
{ id: 'hand', label: 'Hand', shortcut: 'H', cursor: TOOL_CURSORS.hand },
|
|
53
53
|
{ id: 'comment', label: 'Comment', shortcut: 'C', cursor: TOOL_CURSORS.comment },
|
|
54
54
|
{ id: 'pen', label: 'Pen', shortcut: 'B', cursor: TOOL_CURSORS.pen },
|
|
55
|
+
// Annotation polish (item 8) — highlighter sits next to the pen.
|
|
56
|
+
{ id: 'highlighter', label: 'Highlighter', shortcut: 'I', cursor: TOOL_CURSORS.highlighter },
|
|
55
57
|
// Phase 24 — one Shape tool replaces the separate Rect (R) + Ellipse (O)
|
|
56
58
|
// buttons; the primitive is chosen from the palette popover.
|
|
57
59
|
{ id: 'shape', label: 'Shape', shortcut: 'R', cursor: TOOL_CURSORS.shape },
|
|
58
60
|
{ id: 'sticky', label: 'Sticky', shortcut: 'N', cursor: TOOL_CURSORS.sticky },
|
|
61
|
+
// FigJam v3 — labelled organizing container.
|
|
62
|
+
{ id: 'section', label: 'Section', shortcut: '⇧S', cursor: TOOL_CURSORS.shape },
|
|
59
63
|
{ id: 'arrow', label: 'Arrow', shortcut: 'A', cursor: TOOL_CURSORS.arrow },
|
|
60
64
|
{ id: 'text', label: 'Text', shortcut: 'T', cursor: TOOL_CURSORS.text },
|
|
61
65
|
{ id: 'eraser', label: 'Eraser', shortcut: 'E', cursor: TOOL_CURSORS.eraser },
|
|
@@ -112,7 +116,9 @@ export function ToolProvider({
|
|
|
112
116
|
const clearSticky = useCallback(() => {
|
|
113
117
|
setSticky({ tool: null, locked: false });
|
|
114
118
|
}, []);
|
|
115
|
-
|
|
119
|
+
// FigJam v3 — soft default: a fresh Shape tool draws ROUNDED squares (the
|
|
120
|
+
// FigJam look); sharp squares stay one popover click away.
|
|
121
|
+
const [shapeKind, setShapeKind] = useState<ShapeKind>('rounded');
|
|
116
122
|
|
|
117
123
|
// Cursor sync — applied inside the canvas (this hook runs in the canvas
|
|
118
124
|
// context). The active tool's cursor is set on <body> AND forced across the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file use-undo-stack.tsx — React Context + Provider for the undo stack
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/use-undo-stack.tsx
|
|
4
4
|
* @purpose Wraps the pure `undoReducer` (undo-stack.ts) in React state,
|
|
5
5
|
* owns the async runner that awaits side-effects before applying
|
|
6
6
|
* the next state transition, exposes the `lastLabel` HUD signal,
|