@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,156 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file dom-selection.ts — selection-from-DOM helpers (leaf module)
|
|
3
|
-
* @scope plugins/design/dev-server/dom-selection.ts
|
|
4
|
-
* @purpose Pure DOM → Selection builders shared by the canvas chrome
|
|
5
|
-
* (canvas-shell.tsx) and the shell-owned comment mount layer
|
|
6
|
-
* (canvas-comment-mount.tsx). Lives in its own leaf module — no
|
|
7
|
-
* React, no canvas-lib import — so both consumers can lift the
|
|
8
|
-
* same `hoverTargetToSelection` / `deriveFile` logic without a
|
|
9
|
-
* cycle and without bundling the heavy DesignCanvas tree into the
|
|
10
|
-
* lite comment mount.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { HoverTarget } from './input-router.tsx';
|
|
14
|
-
import type { Selection } from './use-selection-set.tsx';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Canvas file path for the current page. Under the mount harness the page is
|
|
18
|
-
* `/_canvas-shell.html?canvas=<rel>&designRel=<root>`; for legacy `.html`
|
|
19
|
-
* mocks it's the served file path itself.
|
|
20
|
-
*/
|
|
21
|
-
export function deriveFile(): string | undefined {
|
|
22
|
-
if (typeof window === 'undefined') return undefined;
|
|
23
|
-
try {
|
|
24
|
-
const p = window.location.pathname;
|
|
25
|
-
if (p === '/_canvas-shell.html' || p === '/_canvas-shell') {
|
|
26
|
-
const qs = new URLSearchParams(window.location.search);
|
|
27
|
-
const canvas = qs.get('canvas') ?? '';
|
|
28
|
-
const designRel = (qs.get('designRel') ?? '.design').replace(/^\/+|\/+$/g, '');
|
|
29
|
-
return canvas ? `${designRel}/${canvas}` : undefined;
|
|
30
|
-
}
|
|
31
|
-
return decodeURIComponent(p).replace(/^\//, '');
|
|
32
|
-
} catch {
|
|
33
|
-
return undefined;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function realClasses(el: Element | null): string {
|
|
38
|
-
if (!el) return '';
|
|
39
|
-
return (el.getAttribute('class') ?? '')
|
|
40
|
-
.trim()
|
|
41
|
-
.split(/\s+/)
|
|
42
|
-
.filter((c) => c && !c.startsWith('dgn-') && !c.startsWith('dc-cv-'))
|
|
43
|
-
.join(' ');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function shortText(el: Element | null, max: number): string {
|
|
47
|
-
if (!el) return '';
|
|
48
|
-
const t = ((el as HTMLElement).innerText || el.textContent || '').replace(/\s+/g, ' ').trim();
|
|
49
|
-
return t.length > max ? `${t.slice(0, max - 1)}…` : t;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function cssPath(el: Element | null): string {
|
|
53
|
-
if (!el) return '';
|
|
54
|
-
const path: string[] = [];
|
|
55
|
-
let cur: Element | null = el;
|
|
56
|
-
while (cur && cur.nodeType === 1 && path.length < 8) {
|
|
57
|
-
const dscEl = cur.getAttribute?.('data-dc-element');
|
|
58
|
-
if (dscEl) {
|
|
59
|
-
path.unshift(`[data-dc-element="${dscEl}"]`);
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
const dscSc = cur.getAttribute?.('data-dc-screen');
|
|
63
|
-
if (dscSc) {
|
|
64
|
-
path.unshift(`[data-dc-screen="${dscSc}"]`);
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
let sel = cur.nodeName.toLowerCase();
|
|
68
|
-
if (cur.id) {
|
|
69
|
-
sel = `#${cur.id}`;
|
|
70
|
-
path.unshift(sel);
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
const cls = realClasses(cur).split(/\s+/).filter(Boolean).slice(0, 2);
|
|
74
|
-
if (cls.length) sel += `.${cls.join('.')}`;
|
|
75
|
-
let sib = 1;
|
|
76
|
-
let n: Element | null = cur.previousElementSibling;
|
|
77
|
-
while (n) {
|
|
78
|
-
sib++;
|
|
79
|
-
n = n.previousElementSibling;
|
|
80
|
-
}
|
|
81
|
-
sel += `:nth-child(${sib})`;
|
|
82
|
-
path.unshift(sel);
|
|
83
|
-
cur = cur.parentElement;
|
|
84
|
-
}
|
|
85
|
-
return path.join(' > ');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function domPath(el: Element | null): string[] {
|
|
89
|
-
const hops: string[] = [];
|
|
90
|
-
let cur = el;
|
|
91
|
-
while (cur && cur.nodeType === 1 && hops.length < 8) {
|
|
92
|
-
let label = cur.nodeName.toLowerCase();
|
|
93
|
-
const dEl = cur.getAttribute?.('data-dc-element');
|
|
94
|
-
const dSc = cur.getAttribute?.('data-dc-screen');
|
|
95
|
-
if (dEl) label += `[data-dc-element="${dEl}"]`;
|
|
96
|
-
else if (dSc) label += `[data-dc-screen="${dSc}"]`;
|
|
97
|
-
else if (cur.id) label += `#${cur.id}`;
|
|
98
|
-
const cls = realClasses(cur).split(/\s+/).filter(Boolean).slice(0, 2);
|
|
99
|
-
if (cls.length && !dEl && !dSc) label += `.${cls.join('.')}`;
|
|
100
|
-
hops.unshift(label);
|
|
101
|
-
cur = cur.parentElement;
|
|
102
|
-
}
|
|
103
|
-
return hops;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function cssEscape(s: string): string {
|
|
107
|
-
// Minimal CSS.escape polyfill — only handles chars actually present in
|
|
108
|
-
// pipeline-stamped IDs (alphanumerics + `-` + `_`).
|
|
109
|
-
return s.replace(/[^a-zA-Z0-9_-]/g, (c) => `\\${c}`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Build the wire-shape `Selection` for a resolved hover target. `file`
|
|
114
|
-
* defaults to `deriveFile()`; the comment mount layer passes it explicitly
|
|
115
|
-
* so all three consumers (router, overlay, mount) agree on the same key.
|
|
116
|
-
*/
|
|
117
|
-
export function hoverTargetToSelection(target: HoverTarget, file?: string): Selection {
|
|
118
|
-
const el = target.el;
|
|
119
|
-
const rect =
|
|
120
|
-
el && (el as HTMLElement).getBoundingClientRect
|
|
121
|
-
? (el as HTMLElement).getBoundingClientRect()
|
|
122
|
-
: null;
|
|
123
|
-
// `cdId` is the hit element's OWN data-cd-id (deep mode); resolver never
|
|
124
|
-
// climbs to an ancestor. Falls back to cssPath of the hit when no stable
|
|
125
|
-
// anchor exists.
|
|
126
|
-
const cdId = target.cdId;
|
|
127
|
-
// Selector resolution order:
|
|
128
|
-
// 1. data-cd-id anchor — stable pipeline-stamped id (preferred).
|
|
129
|
-
// 2. data-dc-screen — chrome click promoted to whole-artboard select
|
|
130
|
-
// (T24.5 G8 multi-artboard gesture).
|
|
131
|
-
// 3. cssPath of the hit — last-resort path string.
|
|
132
|
-
const selector = cdId
|
|
133
|
-
? `[data-cd-id="${cdId}"]`
|
|
134
|
-
: !cdId && target.artboardId
|
|
135
|
-
? `[data-dc-screen="${target.artboardId}"]`
|
|
136
|
-
: cssPath(el);
|
|
137
|
-
return {
|
|
138
|
-
file: file ?? deriveFile(),
|
|
139
|
-
id: cdId ?? undefined,
|
|
140
|
-
selector,
|
|
141
|
-
artboardId: target.artboardId,
|
|
142
|
-
tag: el?.tagName.toLowerCase() ?? '',
|
|
143
|
-
classes: realClasses(el),
|
|
144
|
-
text: shortText(el, 240),
|
|
145
|
-
dom_path: domPath(el),
|
|
146
|
-
bounds: rect
|
|
147
|
-
? {
|
|
148
|
-
x: Math.round(rect.left),
|
|
149
|
-
y: Math.round(rect.top),
|
|
150
|
-
w: Math.round(rect.width),
|
|
151
|
-
h: Math.round(rect.height),
|
|
152
|
-
}
|
|
153
|
-
: null,
|
|
154
|
-
html: el ? (el.outerHTML ?? '').slice(0, 4000) : '',
|
|
155
|
-
};
|
|
156
|
-
}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
// One-time authoritative seed for the shared-doc cutover — Phase 9.2 (DDR-064
|
|
2
|
-
// Task 9). The fix for Risk 1 (the duplication-on-merge trap).
|
|
3
|
-
//
|
|
4
|
-
// The trap (dmonad-confirmed, discuss.yjs.dev/t/.../2538): if a shared doc is
|
|
5
|
-
// populated from TWO independent sources — the local file-seed (room.seed
|
|
6
|
-
// pushing `_comments/<slug>.json` as fresh Y.Array items) AND the hub provider
|
|
7
|
-
// (syncing the hub's canonical items) — the two item-sets have different client
|
|
8
|
-
// IDs, so the CRDT merge CONCATENATES them: comment "c1" appears twice. You
|
|
9
|
-
// cannot fix this with `applyUpdate` of two docs; you must pick ONE authoritative
|
|
10
|
-
// source and build the doc from it.
|
|
11
|
-
//
|
|
12
|
-
// This module is that decision, run ONCE per canvas at cutover, AFTER the
|
|
13
|
-
// provider's first sync (so the doc already holds hub state if the hub had any):
|
|
14
|
-
//
|
|
15
|
-
// - hub HAD state (doc non-empty) → HUB WINS. Leave the doc; the projection
|
|
16
|
-
// materializes hub state to disk. Local divergent files are snapshotted to
|
|
17
|
-
// `_history/` first (rollback) then overwritten by the room persist.
|
|
18
|
-
// - hub was EMPTY (doc empty) → ADOPT. Clear+rebuild the doc from the
|
|
19
|
-
// local files inside `transact(fn, MIGRATION)` (the apply* codecs already
|
|
20
|
-
// delete-then-insert, so this is a true rebuild, not an append). The
|
|
21
|
-
// provider then pushes it up as the canonical first version.
|
|
22
|
-
//
|
|
23
|
-
// Idempotent: re-running with hub state present is a no-op (doc already holds
|
|
24
|
-
// it); re-running an adopt rebuilds from the same files → byte-identical, no
|
|
25
|
-
// duplicate items. The companion guard is the room's seed being disabled for
|
|
26
|
-
// pinned (provider-attached) slugs under sharedDoc (see collab/index.ts
|
|
27
|
-
// `shouldSeed`), so the file-seed can never re-introduce duplicate items after
|
|
28
|
-
// this runs.
|
|
29
|
-
|
|
30
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
31
|
-
import path from 'node:path';
|
|
32
|
-
|
|
33
|
-
import type * as Y from 'yjs';
|
|
34
|
-
|
|
35
|
-
import { Y_TYPES } from '../collab/persistence.ts';
|
|
36
|
-
import {
|
|
37
|
-
applyAnnotationsToDoc,
|
|
38
|
-
applyCommentsToDoc,
|
|
39
|
-
applyCssToDoc,
|
|
40
|
-
applyHtmlToDoc,
|
|
41
|
-
applyMetaToDoc,
|
|
42
|
-
Y_SYNC_TYPES,
|
|
43
|
-
} from './codec.ts';
|
|
44
|
-
import { ORIGINS } from './origins.ts';
|
|
45
|
-
|
|
46
|
-
export interface MigrateSeedPaths {
|
|
47
|
-
html: string;
|
|
48
|
-
comments: string;
|
|
49
|
-
annotations: string;
|
|
50
|
-
meta?: string;
|
|
51
|
-
css?: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface MigrateSeedOptions {
|
|
55
|
-
slug: string;
|
|
56
|
-
doc: Y.Doc;
|
|
57
|
-
paths: MigrateSeedPaths;
|
|
58
|
-
/** `_history/<slug>/` (or any dir) — when set, local files are snapshotted
|
|
59
|
-
* here before a hub-wins overwrite, for rollback. Best-effort. */
|
|
60
|
-
historyDir?: string;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export type MigrateSeedResult = 'hub-wins' | 'local-adopt' | 'empty';
|
|
64
|
-
|
|
65
|
-
/** True when the shared doc holds no synced content for any of the five types. */
|
|
66
|
-
export function docIsEmpty(doc: Y.Doc): boolean {
|
|
67
|
-
if (doc.getText(Y_SYNC_TYPES.html).length > 0) return false;
|
|
68
|
-
if (doc.getText(Y_SYNC_TYPES.css).length > 0) return false;
|
|
69
|
-
if (doc.getText(Y_SYNC_TYPES.meta).length > 0) return false;
|
|
70
|
-
if (doc.getArray(Y_TYPES.comments).length > 0) return false;
|
|
71
|
-
const svg = doc.getMap<unknown>(Y_TYPES.annotations).get('svg');
|
|
72
|
-
if (typeof svg === 'string' && svg.length > 0) return false;
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Run the one-time authoritative seed. Returns which source won so the caller
|
|
78
|
-
* can log / surface a conflict. Safe to call on every boot (idempotent).
|
|
79
|
-
*/
|
|
80
|
-
export function migrateSeed(opts: MigrateSeedOptions): MigrateSeedResult {
|
|
81
|
-
const { doc, paths } = opts;
|
|
82
|
-
|
|
83
|
-
// Hub had canonical state → it wins. The doc already holds it; the projection
|
|
84
|
-
// will materialize it to disk. Snapshot any divergent local files first so the
|
|
85
|
-
// overwrite is recoverable.
|
|
86
|
-
if (!docIsEmpty(doc)) {
|
|
87
|
-
snapshotLocal(opts);
|
|
88
|
-
return 'hub-wins';
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Hub was empty → adopt local. Build the doc from the local files ONCE, inside
|
|
92
|
-
// a single MIGRATION transaction. The apply* codecs delete-then-insert, so
|
|
93
|
-
// this is a clear+rebuild (re-running is a no-op once content matches).
|
|
94
|
-
const localHtml = readLocal(paths.html);
|
|
95
|
-
const localComments = readLocal(paths.comments);
|
|
96
|
-
const localAnnotations = readLocal(paths.annotations);
|
|
97
|
-
const localMeta = paths.meta ? readLocal(paths.meta) : null;
|
|
98
|
-
const localCss = paths.css ? readLocal(paths.css) : null;
|
|
99
|
-
|
|
100
|
-
const hasLocal =
|
|
101
|
-
!!localHtml || !!localComments || !!localAnnotations || !!localMeta || !!localCss;
|
|
102
|
-
if (!hasLocal) return 'empty';
|
|
103
|
-
|
|
104
|
-
doc.transact(() => {
|
|
105
|
-
if (localHtml) applyHtmlToDoc(doc, localHtml, ORIGINS.MIGRATION);
|
|
106
|
-
if (localComments) {
|
|
107
|
-
const parsed = tryParseJsonArray(localComments);
|
|
108
|
-
if (parsed) applyCommentsToDoc(doc, parsed, ORIGINS.MIGRATION);
|
|
109
|
-
}
|
|
110
|
-
if (localAnnotations) applyAnnotationsToDoc(doc, localAnnotations, ORIGINS.MIGRATION);
|
|
111
|
-
if (paths.meta && localMeta) applyMetaToDoc(doc, localMeta, ORIGINS.MIGRATION);
|
|
112
|
-
if (paths.css && localCss) applyCssToDoc(doc, localCss, ORIGINS.MIGRATION);
|
|
113
|
-
}, ORIGINS.MIGRATION);
|
|
114
|
-
|
|
115
|
-
return 'local-adopt';
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/* ---------------------------------------------------------------- helpers */
|
|
119
|
-
|
|
120
|
-
function snapshotLocal(opts: MigrateSeedOptions): void {
|
|
121
|
-
if (!opts.historyDir) return;
|
|
122
|
-
try {
|
|
123
|
-
const dir = path.join(opts.historyDir, 'pre-shared-doc-migration');
|
|
124
|
-
mkdirSync(dir, { recursive: true });
|
|
125
|
-
for (const p of [
|
|
126
|
-
opts.paths.html,
|
|
127
|
-
opts.paths.comments,
|
|
128
|
-
opts.paths.annotations,
|
|
129
|
-
opts.paths.meta,
|
|
130
|
-
opts.paths.css,
|
|
131
|
-
]) {
|
|
132
|
-
if (p && existsSync(p)) {
|
|
133
|
-
copyFileSync(p, path.join(dir, path.basename(p)));
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
} catch {
|
|
137
|
-
/* best-effort — a snapshot failure must not block the cutover */
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function readLocal(p: string): string | null {
|
|
142
|
-
if (!existsSync(p)) return null;
|
|
143
|
-
try {
|
|
144
|
-
return readFileSync(p, 'utf8');
|
|
145
|
-
} catch {
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Proto-pollution-safe (DDR-054 §2g), mirroring the agent + projection comments
|
|
151
|
-
// parse — a planted local `_comments/<slug>.json` must not seed dangerous keys
|
|
152
|
-
// during adopt.
|
|
153
|
-
function tryParseJsonArray(s: string): unknown[] | null {
|
|
154
|
-
try {
|
|
155
|
-
const parsed = JSON.parse(s, (key, value) => {
|
|
156
|
-
if (key === '__proto__' || key === 'constructor' || key === 'prototype') return undefined;
|
|
157
|
-
return value;
|
|
158
|
-
});
|
|
159
|
-
return Array.isArray(parsed) ? parsed : null;
|
|
160
|
-
} catch {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
// canvas-edit.ts — Phase 3.6 Task 5. AST-aware single-attribute edits on TSX
|
|
2
|
-
// canvases. The same component+jsxIndex bookkeeping as canvas-pipeline.ts
|
|
3
|
-
// computes the target ID; tests round-trip an edit through transpileCanvasSource
|
|
4
|
-
// to confirm the IDs line up.
|
|
5
|
-
|
|
6
|
-
import { describe, expect, test } from 'bun:test';
|
|
7
|
-
|
|
8
|
-
import { applyEdit, CanvasEditError, editAttribute } from '../canvas-edit.ts';
|
|
9
|
-
import { transpileCanvasSource } from '../canvas-pipeline.ts';
|
|
10
|
-
|
|
11
|
-
const CANVAS = '/abs/Canvas.tsx';
|
|
12
|
-
|
|
13
|
-
function idsOf(source: string): Record<string, string> {
|
|
14
|
-
// Run the pipeline to learn what data-cd-id each JSX element gets, returning
|
|
15
|
-
// a map keyed by the element-type name so tests can ask "what id is the
|
|
16
|
-
// <span>?" without hardcoding the hash.
|
|
17
|
-
const { withIds } = transpileCanvasSource(CANVAS, source);
|
|
18
|
-
const out: Record<string, string> = {};
|
|
19
|
-
for (const m of withIds.matchAll(/<(\w+)([^>]*?)data-cd-id="([0-9a-f]{8})"/g)) {
|
|
20
|
-
if (!out[m[1] as string]) out[m[1] as string] = m[3] as string;
|
|
21
|
-
}
|
|
22
|
-
return out;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
describe('canvas-edit / applyEdit', () => {
|
|
26
|
-
test('swaps an existing className literal value', () => {
|
|
27
|
-
const src = `function Demo() { return <button className="btn">OK</button>; }`;
|
|
28
|
-
const ids = idsOf(src);
|
|
29
|
-
const id = ids.button as string;
|
|
30
|
-
const out = applyEdit(CANVAS, src, id, 'className', 'btn btn--ghost');
|
|
31
|
-
expect(out.source).toBe(
|
|
32
|
-
`function Demo() { return <button className="btn btn--ghost">OK</button>; }`
|
|
33
|
-
);
|
|
34
|
-
// "btn" (3 chars) → "btn btn--ghost" (14 chars): +11 bytes inside the
|
|
35
|
-
// literal; quotes unchanged.
|
|
36
|
-
expect(out.delta).toBe(11);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('inserts className when missing', () => {
|
|
40
|
-
const src = 'function Demo() { return <div>x</div>; }';
|
|
41
|
-
const ids = idsOf(src);
|
|
42
|
-
const id = ids.div as string;
|
|
43
|
-
const out = applyEdit(CANVAS, src, id, 'className', 'card');
|
|
44
|
-
expect(out.source).toBe(`function Demo() { return <div className="card">x</div>; }`);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('rewrites a plain attribute (aria-label)', () => {
|
|
48
|
-
const src = `function Demo() { return <button aria-label="old">x</button>; }`;
|
|
49
|
-
const ids = idsOf(src);
|
|
50
|
-
const id = ids.button as string;
|
|
51
|
-
const out = applyEdit(CANVAS, src, id, 'aria-label', 'new');
|
|
52
|
-
expect(out.source).toBe(`function Demo() { return <button aria-label="new">x</button>; }`);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test('swaps a single style.<prop> value inside style={{...}}', () => {
|
|
56
|
-
const src = 'function Demo() { return <div style={{ padding: 8, margin: 4 }}>x</div>; }';
|
|
57
|
-
const ids = idsOf(src);
|
|
58
|
-
const id = ids.div as string;
|
|
59
|
-
const out = applyEdit(CANVAS, src, id, 'style.padding', '14');
|
|
60
|
-
expect(out.source).toBe(
|
|
61
|
-
'function Demo() { return <div style={{ padding: 14, margin: 4 }}>x</div>; }'
|
|
62
|
-
);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('inserts a missing style key into existing style={{...}}', () => {
|
|
66
|
-
const src = 'function Demo() { return <div style={{ padding: 8 }}>x</div>; }';
|
|
67
|
-
const ids = idsOf(src);
|
|
68
|
-
const id = ids.div as string;
|
|
69
|
-
const out = applyEdit(CANVAS, src, id, 'style.color', '"#facc15"');
|
|
70
|
-
// The exact spacing of the appended key is implementation-defined; assert
|
|
71
|
-
// the substring lands without trampling the prior key.
|
|
72
|
-
expect(out.source).toContain('padding: 8');
|
|
73
|
-
expect(out.source).toContain('color: "#facc15"');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('inserts a brand-new style={{}} prop when missing', () => {
|
|
77
|
-
const src = 'function Demo() { return <div>x</div>; }';
|
|
78
|
-
const ids = idsOf(src);
|
|
79
|
-
const id = ids.div as string;
|
|
80
|
-
const out = applyEdit(CANVAS, src, id, 'style.padding', '14');
|
|
81
|
-
expect(out.source).toContain('<div style={{ padding: 14 }}>x</div>');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('throws CanvasEditError when id is not found', () => {
|
|
85
|
-
const src = 'function Demo() { return <div>x</div>; }';
|
|
86
|
-
expect(() => applyEdit(CANVAS, src, 'deadbeef', 'className', 'x')).toThrow(CanvasEditError);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('refuses to edit data-cd-id (owned by pipeline)', () => {
|
|
90
|
-
const src = 'function Demo() { return <div>x</div>; }';
|
|
91
|
-
const ids = idsOf(src);
|
|
92
|
-
const id = ids.div as string;
|
|
93
|
-
expect(() => applyEdit(CANVAS, src, id, 'data-cd-id', 'beefcafe')).toThrow(CanvasEditError);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('throws on malformed source', () => {
|
|
97
|
-
const src = 'function Demo() { return <not closing';
|
|
98
|
-
expect(() => applyEdit(CANVAS, src, 'aaaaaaaa', 'className', 'x')).toThrow();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('targets the right element when multiple share a type', () => {
|
|
102
|
-
const src =
|
|
103
|
-
'function Demo() { return (<section>' +
|
|
104
|
-
`<button className="a">A</button>` +
|
|
105
|
-
`<button className="b">B</button>` +
|
|
106
|
-
'</section>); }';
|
|
107
|
-
// The pipeline visits in pre-order; expose all <button> ids by walking the
|
|
108
|
-
// withIds output and capturing each match.
|
|
109
|
-
const { withIds } = transpileCanvasSource(CANVAS, src);
|
|
110
|
-
const matches = [
|
|
111
|
-
...withIds.matchAll(/<button[^>]*data-cd-id="([0-9a-f]{8})"[^>]*className="([ab])"/g),
|
|
112
|
-
];
|
|
113
|
-
expect(matches.length).toBe(2);
|
|
114
|
-
const idA = matches.find((m) => m[2] === 'a')?.[1] as string;
|
|
115
|
-
expect(idA).toBeDefined();
|
|
116
|
-
const out = applyEdit(CANVAS, src, idA, 'className', 'A-edited');
|
|
117
|
-
expect(out.source).toContain('className="A-edited"');
|
|
118
|
-
expect(out.source).toContain('className="b"');
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe('canvas-edit / editAttribute (fs)', () => {
|
|
123
|
-
test('writes the edit atomically + is a no-op when source unchanged', async () => {
|
|
124
|
-
const tmp = `/tmp/canvas-edit-${Math.random().toString(36).slice(2)}.tsx`;
|
|
125
|
-
const src = `function Demo() { return <div className="x">y</div>; }`;
|
|
126
|
-
await Bun.write(tmp, src);
|
|
127
|
-
const ids = idsOf(src);
|
|
128
|
-
const id = ids.div as string;
|
|
129
|
-
|
|
130
|
-
const r1 = await editAttribute(tmp, id, 'className', 'longer-value');
|
|
131
|
-
expect(r1.delta).toBeGreaterThan(0);
|
|
132
|
-
const after = await Bun.file(tmp).text();
|
|
133
|
-
expect(after).toContain('className="longer-value"');
|
|
134
|
-
|
|
135
|
-
// Second pass with the same value — no-op (the source already has it).
|
|
136
|
-
const r2 = await editAttribute(tmp, id, 'className', 'longer-value');
|
|
137
|
-
expect(r2.delta).toBe(0);
|
|
138
|
-
});
|
|
139
|
-
});
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
// Phase 9.2 (DDR-064) Phase E — the one-time authoritative seed (Task 9).
|
|
2
|
-
//
|
|
3
|
-
// Pins the fix for Risk 1, the duplication-on-merge trap: a shared doc must be
|
|
4
|
-
// populated from ONE source. These tests prove migrateSeed never duplicates
|
|
5
|
-
// comments and is idempotent across re-runs (server restarts).
|
|
6
|
-
|
|
7
|
-
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
8
|
-
import { mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
9
|
-
import { tmpdir } from 'node:os';
|
|
10
|
-
import { join } from 'node:path';
|
|
11
|
-
import * as Y from 'yjs';
|
|
12
|
-
|
|
13
|
-
import { createPersistence, Y_TYPES } from '../collab/persistence.ts';
|
|
14
|
-
import type { Context } from '../context.ts';
|
|
15
|
-
import { applyCommentsToDoc, applyHtmlToDoc } from '../sync/codec.ts';
|
|
16
|
-
import { docIsEmpty, migrateSeed } from '../sync/migrate-seed.ts';
|
|
17
|
-
|
|
18
|
-
let dir: string;
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
dir = mkdtempSync(join(tmpdir(), 'migrate-seed-'));
|
|
21
|
-
});
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
rmSync(dir, { recursive: true, force: true });
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
function paths() {
|
|
27
|
-
return {
|
|
28
|
-
html: join(dir, 'screen.html'),
|
|
29
|
-
comments: join(dir, 'screen.comments.json'),
|
|
30
|
-
annotations: join(dir, 'screen.annotations.svg'),
|
|
31
|
-
meta: join(dir, 'screen.meta.json'),
|
|
32
|
-
css: join(dir, 'screen.css'),
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
describe('docIsEmpty', () => {
|
|
37
|
-
test('true for a fresh doc, false once any type is populated', () => {
|
|
38
|
-
const doc = new Y.Doc();
|
|
39
|
-
expect(docIsEmpty(doc)).toBe(true);
|
|
40
|
-
doc.getArray(Y_TYPES.comments).push([{ id: 'c1' }]);
|
|
41
|
-
expect(docIsEmpty(doc)).toBe(false);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
describe('migrateSeed — adopt (hub was empty)', () => {
|
|
46
|
-
test('seeds an empty doc from local files and reports local-adopt', () => {
|
|
47
|
-
const p = paths();
|
|
48
|
-
writeFileSync(p.html, '<main>local body</main>');
|
|
49
|
-
writeFileSync(p.comments, JSON.stringify([{ id: 'c1', text: 'local' }]));
|
|
50
|
-
|
|
51
|
-
const doc = new Y.Doc();
|
|
52
|
-
const result = migrateSeed({ slug: 's', doc, paths: p });
|
|
53
|
-
|
|
54
|
-
expect(result).toBe('local-adopt');
|
|
55
|
-
expect(doc.getText('html').toString()).toBe('<main>local body</main>');
|
|
56
|
-
expect(doc.getArray(Y_TYPES.comments).toArray()).toEqual([{ id: 'c1', text: 'local' }]);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('empty doc + no local files → "empty" (nothing to seed)', () => {
|
|
60
|
-
const doc = new Y.Doc();
|
|
61
|
-
expect(migrateSeed({ slug: 's', doc, paths: paths() })).toBe('empty');
|
|
62
|
-
expect(docIsEmpty(doc)).toBe(true);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe('migrateSeed — idempotent (no duplication on re-run)', () => {
|
|
67
|
-
test('adopting twice leaves comments un-duplicated', () => {
|
|
68
|
-
const p = paths();
|
|
69
|
-
writeFileSync(
|
|
70
|
-
p.comments,
|
|
71
|
-
JSON.stringify([
|
|
72
|
-
{ id: 'c1', text: 'x' },
|
|
73
|
-
{ id: 'c2', text: 'y' },
|
|
74
|
-
])
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
const doc = new Y.Doc();
|
|
78
|
-
expect(migrateSeed({ slug: 's', doc, paths: p })).toBe('local-adopt');
|
|
79
|
-
expect(doc.getArray(Y_TYPES.comments).length).toBe(2);
|
|
80
|
-
|
|
81
|
-
// Second run (e.g. server restart): the doc is now non-empty → hub-wins
|
|
82
|
-
// branch → no re-adopt → still exactly 2 comments, byte-identical.
|
|
83
|
-
expect(migrateSeed({ slug: 's', doc, paths: p })).toBe('hub-wins');
|
|
84
|
-
expect(doc.getArray(Y_TYPES.comments).toArray()).toEqual([
|
|
85
|
-
{ id: 'c1', text: 'x' },
|
|
86
|
-
{ id: 'c2', text: 'y' },
|
|
87
|
-
]);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
describe('room file-seed disable (the other half of the Risk-1 fix)', () => {
|
|
92
|
-
test('persistence.seed is a no-op for a pinned slug — never touches api or the doc', async () => {
|
|
93
|
-
let apiCalled = false;
|
|
94
|
-
// Minimal api stub; seed must short-circuit before reaching it.
|
|
95
|
-
const api = {
|
|
96
|
-
loadCommentsForFile: async () => {
|
|
97
|
-
apiCalled = true;
|
|
98
|
-
return [];
|
|
99
|
-
},
|
|
100
|
-
loadAnnotations: async () => {
|
|
101
|
-
apiCalled = true;
|
|
102
|
-
return null;
|
|
103
|
-
},
|
|
104
|
-
} as unknown as import('../api.ts').Api;
|
|
105
|
-
const ctx = { paths: { designRoot: dir } } as unknown as Context;
|
|
106
|
-
|
|
107
|
-
const persistence = createPersistence({
|
|
108
|
-
ctx,
|
|
109
|
-
api,
|
|
110
|
-
fileForSlug: async () => 'ui/x.html',
|
|
111
|
-
// Mirrors createCollab's predicate: don't seed a pinned (provider-attached)
|
|
112
|
-
// slug under sharedDoc — the migrate-seed + provider own population.
|
|
113
|
-
shouldSeed: (slug) => slug !== 'pinned',
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
const doc = new Y.Doc();
|
|
117
|
-
await persistence.seed('pinned', doc);
|
|
118
|
-
// Returned before touching the api or mutating the doc → no duplicate items
|
|
119
|
-
// can be introduced by the file-seed after the migrate-seed ran.
|
|
120
|
-
expect(apiCalled).toBe(false);
|
|
121
|
-
expect(docIsEmpty(doc)).toBe(true);
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
describe('migrateSeed — hub-wins (the duplication trap avoided)', () => {
|
|
126
|
-
test('a doc already holding hub comments is NOT merged with the local file → no dup', () => {
|
|
127
|
-
const p = paths();
|
|
128
|
-
// Local file (e.g. copied via git) holds the SAME logical comment id…
|
|
129
|
-
writeFileSync(p.comments, JSON.stringify([{ id: 'c1', text: 'from disk' }]));
|
|
130
|
-
|
|
131
|
-
// …but the doc already received it from the hub (a DIFFERENT CRDT item).
|
|
132
|
-
const doc = new Y.Doc();
|
|
133
|
-
applyCommentsToDoc(doc, [{ id: 'c1', text: 'from hub' }], 'hub');
|
|
134
|
-
|
|
135
|
-
const result = migrateSeed({ slug: 's', doc, paths: p });
|
|
136
|
-
|
|
137
|
-
// Hub wins; the local file is NOT pushed as a second item — exactly ONE c1.
|
|
138
|
-
expect(result).toBe('hub-wins');
|
|
139
|
-
expect(doc.getArray(Y_TYPES.comments).toArray()).toEqual([{ id: 'c1', text: 'from hub' }]);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test('snapshots divergent local files to historyDir before the hub-wins overwrite', () => {
|
|
143
|
-
const p = paths();
|
|
144
|
-
writeFileSync(p.html, '<main>LOCAL divergent</main>');
|
|
145
|
-
writeFileSync(p.comments, JSON.stringify([{ id: 'local' }]));
|
|
146
|
-
|
|
147
|
-
const doc = new Y.Doc();
|
|
148
|
-
applyHtmlToDoc(doc, '<main>HUB</main>', 'hub'); // doc holds hub state
|
|
149
|
-
|
|
150
|
-
const historyDir = join(dir, '_history', 's');
|
|
151
|
-
const result = migrateSeed({ slug: 's', doc, paths: p, historyDir });
|
|
152
|
-
|
|
153
|
-
expect(result).toBe('hub-wins');
|
|
154
|
-
// The pre-migration snapshot captured the local files for rollback.
|
|
155
|
-
const snapped = readdirSync(join(historyDir, 'pre-shared-doc-migration'));
|
|
156
|
-
expect(snapped).toContain('screen.html');
|
|
157
|
-
expect(snapped).toContain('screen.comments.json');
|
|
158
|
-
});
|
|
159
|
-
});
|