@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
|
@@ -19,7 +19,8 @@ import type { ActiveJsonShape } from './exporters/scope.ts';
|
|
|
19
19
|
import type { Inspect } from './inspect.ts';
|
|
20
20
|
import { canvasSlug, writeLocator } from './locator.ts';
|
|
21
21
|
import { DEV_SERVER_ROOT } from './paths.ts';
|
|
22
|
-
import { getRuntimeBundle, packageForSlug
|
|
22
|
+
import { getRuntimeBundle, packageForSlug } from './runtime-bundle.ts';
|
|
23
|
+
import { loadWhatsNew } from './whats-new.ts';
|
|
23
24
|
|
|
24
25
|
// Real disk install root — never the virtual `/$bunfs/root` of compiled bins.
|
|
25
26
|
// See paths.ts for the resolution logic + Phase 19.1 / v0.18.1 rationale.
|
|
@@ -80,10 +81,11 @@ function ext(p: string): string {
|
|
|
80
81
|
*
|
|
81
82
|
* `frame-ancestors` (A6) — restricts who may embed the canvas document. The
|
|
82
83
|
* legit embedder is the main dev-server origin, so we allowlist exactly that
|
|
83
|
-
* (`mainOrigin`
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
84
|
+
* (`mainOrigin` — a space-separated source list; server.ts advertises both
|
|
85
|
+
* loopback spellings, `localhost` + `127.0.0.1`) plus `'self'`; an arbitrary
|
|
86
|
+
* external page can no longer reframe the canvas. When `mainOrigin` is unknown
|
|
87
|
+
* (tests / pre-boot) the directive is OMITTED rather than set to `'self'` —
|
|
88
|
+
* `'self'` alone would forbid the legit cross-origin embed and blank the canvas.
|
|
87
89
|
*/
|
|
88
90
|
export function cspForCanvasShell(html: string, mainOrigin?: string): string {
|
|
89
91
|
const hashes: string[] = [];
|
|
@@ -114,6 +116,34 @@ export function cspForCanvasShell(html: string, mainOrigin?: string): string {
|
|
|
114
116
|
return directives.join('; ');
|
|
115
117
|
}
|
|
116
118
|
|
|
119
|
+
/**
|
|
120
|
+
* CSRF guard for the main-origin source-write routes (edit-css / edit-text /
|
|
121
|
+
* edit-attr). Those routes are reachable only from the shell, which is
|
|
122
|
+
* same-origin — but `readJson` enforces no `Content-Type`, so a cross-site page
|
|
123
|
+
* could otherwise forge a `text/plain` CORS *simple-request* POST to
|
|
124
|
+
* `http://localhost:<port>/_api/edit-*` (no preflight) and drive a write into
|
|
125
|
+
* the user's source `.tsx`. The browser stamps every cross-origin POST with an
|
|
126
|
+
* unspoofable `Origin` header, so we reject any request whose Origin is PRESENT
|
|
127
|
+
* and ≠ the server's own origin. A request with NO Origin (bun:test, curl,
|
|
128
|
+
* non-browser programmatic clients — none of which are the CSRF threat, which
|
|
129
|
+
* requires a browser executing attacker markup that always sends Origin on a
|
|
130
|
+
* cross-origin POST) is allowed through. This is layered on top of the DDR-054
|
|
131
|
+
* origin-split (which blocks the untrusted canvas *iframe*); it closes the
|
|
132
|
+
* *other* untrusted origin — a malicious top-level page in another tab.
|
|
133
|
+
* Deliberately NOT applied to `/_api/asset` (that route is canvas-origin
|
|
134
|
+
* reachable by design — drag-drop/paste upload runs inside the iframe). See
|
|
135
|
+
* DDR-105. Exported for unit testing (the decision is a pure function of `req`).
|
|
136
|
+
*/
|
|
137
|
+
export function sameOriginWrite(req: Request): boolean {
|
|
138
|
+
const origin = req.headers.get('origin');
|
|
139
|
+
if (!origin) return true; // non-browser / same-origin omitting Origin → allow
|
|
140
|
+
try {
|
|
141
|
+
return origin === new URL(req.url).origin;
|
|
142
|
+
} catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
117
147
|
function safePathUnderRoot(reqUrl: string, repoRoot: string): string | null {
|
|
118
148
|
let pathname: string;
|
|
119
149
|
try {
|
|
@@ -131,7 +161,10 @@ function safePathUnderRoot(reqUrl: string, repoRoot: string): string | null {
|
|
|
131
161
|
// and inside the standalone binary's embedded FS in --compile mode.
|
|
132
162
|
const DIST_DIR = join(HERE, 'dist');
|
|
133
163
|
const CLIENT_DIR = join(HERE, 'client');
|
|
134
|
-
|
|
164
|
+
// Templates stayed under the design *plugin* (plugins/design/templates) when the
|
|
165
|
+
// dev-server moved to apps/studio (DDR-095) — both ship in the npm tarball, so
|
|
166
|
+
// from DEV_SERVER_ROOT (apps/studio) reach across with ../../plugins/design.
|
|
167
|
+
const TEMPLATES_DIR = join(HERE, '..', '..', 'plugins', 'design', 'templates');
|
|
135
168
|
|
|
136
169
|
// In-memory transpile cache. Key = absolute canvas path. Repeat GETs against an
|
|
137
170
|
// unchanged source skip the parse + ID-injection + Bun.Transpiler + Bun.build
|
|
@@ -424,6 +457,12 @@ export function createHttp(ctx: Context, api: Api, inspect: Inspect, ai: AiActiv
|
|
|
424
457
|
|
|
425
458
|
'/_config': () => Response.json({ ...ctx.cfg, canvasOrigin: ctx.canvasOrigin }),
|
|
426
459
|
|
|
460
|
+
// What's New feed (DDR-A) — read-only product-update list surfaced in the
|
|
461
|
+
// Maude UI chrome. Main-origin only by omission from the canvas-origin
|
|
462
|
+
// allowlist below (the untrusted canvas iframe never needs it).
|
|
463
|
+
'/_api/whats-new': () =>
|
|
464
|
+
Response.json(loadWhatsNew(), { headers: { 'Cache-Control': 'no-store' } }),
|
|
465
|
+
|
|
427
466
|
'/_index-data': async () =>
|
|
428
467
|
Response.json(await api.buildIndexData(), { headers: { 'Cache-Control': 'no-store' } }),
|
|
429
468
|
|
|
@@ -582,6 +621,181 @@ export function createHttp(ctx: Context, api: Api, inspect: Inspect, ai: AiActiv
|
|
|
582
621
|
return new Response('Method not allowed', { status: 405 });
|
|
583
622
|
},
|
|
584
623
|
|
|
624
|
+
'/_api/canvas': async (req: Request) => {
|
|
625
|
+
// Phase 22 — create + soft-delete a canvas from the browser file tree.
|
|
626
|
+
// POST body { name, kind?: "brief-board", group? } → 201 { file, rel, slug }
|
|
627
|
+
// DELETE ?file=<rel> → 200 { rel, slug, trashed[] }
|
|
628
|
+
// MAIN ORIGIN ONLY: this route is intentionally absent from
|
|
629
|
+
// startCanvasServer's allowlist (DDR-054) — the untrusted canvas iframe
|
|
630
|
+
// origin must never reach a file-write/-delete endpoint. Validation lives in
|
|
631
|
+
// api.createCanvas / api.deleteCanvas (containment + group allowlist).
|
|
632
|
+
if (req.method === 'DELETE') {
|
|
633
|
+
const file = new URL(req.url).searchParams.get('file');
|
|
634
|
+
const result = await api.deleteCanvas({ file });
|
|
635
|
+
if (!result.ok) {
|
|
636
|
+
return Response.json(
|
|
637
|
+
{ ok: false, error: result.error },
|
|
638
|
+
{ status: result.status, headers: { 'Cache-Control': 'no-store' } }
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
return Response.json(
|
|
642
|
+
{
|
|
643
|
+
ok: true,
|
|
644
|
+
rel: result.rel,
|
|
645
|
+
slug: result.slug,
|
|
646
|
+
trashed: result.trashed,
|
|
647
|
+
trashDir: result.trashDir,
|
|
648
|
+
},
|
|
649
|
+
{ status: 200, headers: { 'Cache-Control': 'no-store' } }
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
if (req.method !== 'POST') return new Response('Method not allowed', { status: 405 });
|
|
653
|
+
const body = await readJson<{ name?: unknown; kind?: unknown; group?: unknown }>(
|
|
654
|
+
req,
|
|
655
|
+
8 * 1024
|
|
656
|
+
);
|
|
657
|
+
if (!body) return new Response('body required', { status: 400 });
|
|
658
|
+
const result = await api.createCanvas(body);
|
|
659
|
+
if (!result.ok) {
|
|
660
|
+
return Response.json(
|
|
661
|
+
{ ok: false, error: result.error },
|
|
662
|
+
{ status: result.status, headers: { 'Cache-Control': 'no-store' } }
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
return Response.json(
|
|
666
|
+
{ ok: true, file: result.file, rel: result.rel, slug: result.slug },
|
|
667
|
+
{ status: 201, headers: { 'Cache-Control': 'no-store' } }
|
|
668
|
+
);
|
|
669
|
+
},
|
|
670
|
+
|
|
671
|
+
'/_api/edit-css': async (req: Request) => {
|
|
672
|
+
// Phase 12 (DDR-103) — single-property inline CSS edit. POST body
|
|
673
|
+
// { canvas, id, property, value } → writes one key into the element's
|
|
674
|
+
// inline `style={{}}` object via api.editCss → editAttribute. MAIN ORIGIN
|
|
675
|
+
// ONLY: intentionally absent from CANVAS_SAFE_API + startCanvasServer's
|
|
676
|
+
// route allowlist (DDR-054) — the untrusted canvas iframe origin must never
|
|
677
|
+
// reach a source-write endpoint. The CSS-knob UI lives in the shell (main
|
|
678
|
+
// origin) and calls it directly.
|
|
679
|
+
if (req.method !== 'POST') return new Response('Method not allowed', { status: 405 });
|
|
680
|
+
if (!sameOriginWrite(req))
|
|
681
|
+
return new Response('cross-origin write rejected', { status: 403 });
|
|
682
|
+
const body = await readJson<{
|
|
683
|
+
canvas?: unknown;
|
|
684
|
+
id?: unknown;
|
|
685
|
+
property?: unknown;
|
|
686
|
+
value?: unknown;
|
|
687
|
+
reset?: unknown;
|
|
688
|
+
}>(req, 8 * 1024);
|
|
689
|
+
if (!body) return new Response('body required', { status: 400 });
|
|
690
|
+
const result = await api.editCss(body);
|
|
691
|
+
if (!result.ok) {
|
|
692
|
+
return Response.json(
|
|
693
|
+
{ ok: false, error: result.error },
|
|
694
|
+
{ status: result.status, headers: { 'Cache-Control': 'no-store' } }
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
return Response.json(
|
|
698
|
+
{ ok: true, delta: result.delta },
|
|
699
|
+
{ status: 200, headers: { 'Cache-Control': 'no-store' } }
|
|
700
|
+
);
|
|
701
|
+
},
|
|
702
|
+
|
|
703
|
+
'/_api/edit-text': async (req: Request) => {
|
|
704
|
+
// Phase 12 (DDR-103) — inline element text-content edit. POST body
|
|
705
|
+
// { canvas, id, text } → overwrites the element's single JSXText child
|
|
706
|
+
// (JSX-escaped) via api.editText → editText. Same MAIN-ORIGIN-ONLY trust
|
|
707
|
+
// boundary as /_api/edit-css + /_api/canvas. The inline contenteditable
|
|
708
|
+
// editor reaches it via the dgn:* bus → shell relay (never the iframe).
|
|
709
|
+
if (req.method !== 'POST') return new Response('Method not allowed', { status: 405 });
|
|
710
|
+
if (!sameOriginWrite(req))
|
|
711
|
+
return new Response('cross-origin write rejected', { status: 403 });
|
|
712
|
+
const body = await readJson<{ canvas?: unknown; id?: unknown; text?: unknown }>(
|
|
713
|
+
req,
|
|
714
|
+
16 * 1024
|
|
715
|
+
);
|
|
716
|
+
if (!body) return new Response('body required', { status: 400 });
|
|
717
|
+
const result = await api.editText(body);
|
|
718
|
+
if (!result.ok) {
|
|
719
|
+
return Response.json(
|
|
720
|
+
{ ok: false, error: result.error },
|
|
721
|
+
{ status: result.status, headers: { 'Cache-Control': 'no-store' } }
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
return Response.json(
|
|
725
|
+
{ ok: true, delta: result.delta },
|
|
726
|
+
{ status: 200, headers: { 'Cache-Control': 'no-store' } }
|
|
727
|
+
);
|
|
728
|
+
},
|
|
729
|
+
|
|
730
|
+
'/_api/edit-attr': async (req: Request) => {
|
|
731
|
+
// Phase 12.2 (DDR-104) — the CSS panel's "custom HTML attribute" escape
|
|
732
|
+
// hatch. POST body { canvas, id, attr, value } → writes a plain JSX
|
|
733
|
+
// attribute (data-*, aria-*, role, …) via api.editAttr → editAttribute's
|
|
734
|
+
// non-`style.` path. Same MAIN-ORIGIN-ONLY trust boundary as
|
|
735
|
+
// /_api/edit-css + /_api/edit-text (absent from CANVAS_SAFE_API +
|
|
736
|
+
// startCanvasServer's route map; the untrusted iframe can never reach it).
|
|
737
|
+
if (req.method !== 'POST') return new Response('Method not allowed', { status: 405 });
|
|
738
|
+
if (!sameOriginWrite(req))
|
|
739
|
+
return new Response('cross-origin write rejected', { status: 403 });
|
|
740
|
+
const body = await readJson<{
|
|
741
|
+
canvas?: unknown;
|
|
742
|
+
id?: unknown;
|
|
743
|
+
attr?: unknown;
|
|
744
|
+
value?: unknown;
|
|
745
|
+
reset?: unknown;
|
|
746
|
+
}>(req, 8 * 1024);
|
|
747
|
+
if (!body) return new Response('body required', { status: 400 });
|
|
748
|
+
const result = await api.editAttr(body);
|
|
749
|
+
if (!result.ok) {
|
|
750
|
+
return Response.json(
|
|
751
|
+
{ ok: false, error: result.error },
|
|
752
|
+
{ status: result.status, headers: { 'Cache-Control': 'no-store' } }
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
return Response.json(
|
|
756
|
+
{ ok: true, delta: result.delta },
|
|
757
|
+
{ status: 200, headers: { 'Cache-Control': 'no-store' } }
|
|
758
|
+
);
|
|
759
|
+
},
|
|
760
|
+
|
|
761
|
+
'/_api/asset': async (req: Request) => {
|
|
762
|
+
// Phase 23 — binary image upload from the canvas (drag-drop / paste / the
|
|
763
|
+
// a11y file picker). POST raw image bytes → content-addressed write under
|
|
764
|
+
// <designRoot>/assets/<sha8>.<ext>, returns 201 { path }. This route is on
|
|
765
|
+
// the canvas-origin allowlist (CANVAS_SAFE_API below) — a bigger grant than
|
|
766
|
+
// the inert annotation-SVG write (binary, disk) — so the caps in
|
|
767
|
+
// api.saveAsset (magic-byte sniff, 10 MB ceiling, content-addressed name,
|
|
768
|
+
// traversal guard, no-SVG, dedupe) are the load-bearing trust mitigation,
|
|
769
|
+
// NOT optional. See DDR (Task 9).
|
|
770
|
+
if (req.method !== 'POST') return new Response('Method not allowed', { status: 405 });
|
|
771
|
+
// Early reject on a declared oversize body (the post-read length check in
|
|
772
|
+
// saveAsset is the authoritative gate — Content-Length can be omitted/lied).
|
|
773
|
+
const declared = Number(req.headers.get('content-length') || '0');
|
|
774
|
+
if (Number.isFinite(declared) && declared > 10 * 1024 * 1024) {
|
|
775
|
+
return Response.json(
|
|
776
|
+
{ ok: false, error: 'asset exceeds the 10 MB cap' },
|
|
777
|
+
{ status: 413, headers: { 'Cache-Control': 'no-store' } }
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
let bytes: Uint8Array;
|
|
781
|
+
try {
|
|
782
|
+
bytes = new Uint8Array(await req.arrayBuffer());
|
|
783
|
+
} catch {
|
|
784
|
+
return new Response('could not read request body', { status: 400 });
|
|
785
|
+
}
|
|
786
|
+
const result = await api.saveAsset(bytes);
|
|
787
|
+
if (!result.ok) {
|
|
788
|
+
return Response.json(
|
|
789
|
+
{ ok: false, error: result.error },
|
|
790
|
+
{ status: result.status ?? 400, headers: { 'Cache-Control': 'no-store' } }
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
return Response.json(
|
|
794
|
+
{ path: result.path },
|
|
795
|
+
{ status: 201, headers: { 'Cache-Control': 'no-store' } }
|
|
796
|
+
);
|
|
797
|
+
},
|
|
798
|
+
|
|
585
799
|
'/_api/export-history': async (req: Request) => {
|
|
586
800
|
// Phase 6.5 T10 — read-only recent-exports feed for the dialog's
|
|
587
801
|
// Recent tab. Writes happen as a side-effect of `/_api/export`.
|
|
@@ -791,6 +1005,11 @@ export function createHttp(ctx: Context, api: Api, inspect: Inspect, ai: AiActiv
|
|
|
791
1005
|
headers: {
|
|
792
1006
|
'Content-Type': MIME[e] || 'application/octet-stream',
|
|
793
1007
|
'Cache-Control': 'no-store',
|
|
1008
|
+
// DDR-088 follow-up — never let a browser MIME-sniff a served file
|
|
1009
|
+
// (e.g. an uploaded GIF/WEBP polyglot) into a richer type. Assets are
|
|
1010
|
+
// only referenced via <image href> + the canvas CSP blocks script, so
|
|
1011
|
+
// this is defense-in-depth on the static lane.
|
|
1012
|
+
'X-Content-Type-Options': 'nosniff',
|
|
794
1013
|
},
|
|
795
1014
|
});
|
|
796
1015
|
} catch (err) {
|
|
@@ -837,6 +1056,7 @@ export function createHttp(ctx: Context, api: Api, inspect: Inspect, ai: AiActiv
|
|
|
837
1056
|
'/_api/git-user', // presence display name
|
|
838
1057
|
'/_api/canvas-meta', // layout/viewport sidecar (GET + PATCH)
|
|
839
1058
|
'/_api/annotations', // annotation SVG (GET + PUT) — drives the collab bridge
|
|
1059
|
+
'/_api/asset', // Phase 23 — capped binary image upload (sniff+10MB+sha8 name+no-SVG)
|
|
840
1060
|
'/_api/git-committers', // @mention autocomplete
|
|
841
1061
|
'/_api/ai', // AI-activity banner
|
|
842
1062
|
'/_comments', // per-file comment list (renders pins)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file input-router.tsx — canvas pointer/keyboard classifier + hook
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/input-router.tsx
|
|
4
4
|
* @purpose Owned by canvas-lib's DesignCanvas. Classifies the NON-WHEEL
|
|
5
5
|
* subset of pointer + key events into discrete router actions.
|
|
6
6
|
* `useViewportController` keeps owning wheel + middle-mouse +
|
|
@@ -68,6 +68,10 @@ export type Tool =
|
|
|
68
68
|
| 'hand'
|
|
69
69
|
| 'comment'
|
|
70
70
|
| 'pen'
|
|
71
|
+
// Annotation polish (item 8) — FigJam-style highlighter. A `pen`-shaped tool
|
|
72
|
+
// that produces a translucent wide multiply stroke (a PenStroke with
|
|
73
|
+
// `highlighter:true`), not a new stroke type.
|
|
74
|
+
| 'highlighter'
|
|
71
75
|
// Phase 24 — `shape` is the single active draw tool that produces rect /
|
|
72
76
|
// ellipse / polygon strokes (the kind is chosen via the palette popover).
|
|
73
77
|
// `rect` / `ellipse` stay in the union as the stroke discriminants they
|
|
@@ -78,16 +82,20 @@ export type Tool =
|
|
|
78
82
|
| 'arrow'
|
|
79
83
|
| 'sticky'
|
|
80
84
|
| 'text'
|
|
85
|
+
// FigJam v3 — labelled organizing container (Shift+S).
|
|
86
|
+
| 'section'
|
|
81
87
|
| 'eraser';
|
|
82
88
|
|
|
83
89
|
const ANNOTATION_TOOLS = new Set<Tool>([
|
|
84
90
|
'pen',
|
|
91
|
+
'highlighter',
|
|
85
92
|
'shape',
|
|
86
93
|
'rect',
|
|
87
94
|
'ellipse',
|
|
88
95
|
'arrow',
|
|
89
96
|
'sticky',
|
|
90
97
|
'text',
|
|
98
|
+
'section',
|
|
91
99
|
'eraser',
|
|
92
100
|
]);
|
|
93
101
|
|
|
@@ -167,6 +175,8 @@ export function classify(input: ClassifyInput): RouterAction {
|
|
|
167
175
|
if (k === 'h') return { kind: 'tool', tool: 'hand' };
|
|
168
176
|
if (k === 'c') return { kind: 'tool', tool: 'comment' };
|
|
169
177
|
if (k === 'b') return { kind: 'tool', tool: 'pen' };
|
|
178
|
+
// I = hIghlighter (a free bare letter; 'H' is taken by Hand).
|
|
179
|
+
if (k === 'i') return { kind: 'tool', tool: 'highlighter' };
|
|
170
180
|
// Phase 24 — R (and legacy O) both arm the single Shape tool; the specific
|
|
171
181
|
// primitive is picked from the palette's shape-kind popover.
|
|
172
182
|
if (k === 'r' || k === 'o') return { kind: 'tool', tool: 'shape' };
|
|
@@ -176,6 +186,10 @@ export function classify(input: ClassifyInput): RouterAction {
|
|
|
176
186
|
// yields when focus is inside the canvas iframe (app.jsx onKey bail).
|
|
177
187
|
if (k === 'n') return { kind: 'tool', tool: 'sticky' };
|
|
178
188
|
if (k === 't') return { kind: 'tool', tool: 'text' };
|
|
189
|
+
// FigJam v3 — Shift+S arms the Section tool (FigJam's own binding; bare S
|
|
190
|
+
// stays with the shell's Design-system view). Checked here because the
|
|
191
|
+
// modifier guard above only filters Cmd/Ctrl/Alt.
|
|
192
|
+
if (k === 's' && input.shiftKey) return { kind: 'tool', tool: 'section' };
|
|
179
193
|
if (k === 'e') return { kind: 'tool', tool: 'eraser' };
|
|
180
194
|
if (input.key === 'Escape') return { kind: 'escape' };
|
|
181
195
|
return { kind: 'no-op' };
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
// Active-canvas state, selected-element tracking, and HTML injection
|
|
2
2
|
// (inspector overlay + canvas runtime). See plan Task 7 + DDR-007.
|
|
3
3
|
|
|
4
|
-
import path from 'node:path';
|
|
5
4
|
import type { Context } from './context.ts';
|
|
6
5
|
|
|
7
6
|
export interface SelectedElement {
|
|
8
7
|
file: string;
|
|
9
8
|
/** CSS-selector path (v1 anchor). Always present for backwards-compat + legacy HTML canvases. */
|
|
10
9
|
selector: string;
|
|
10
|
+
/** Occurrence index among `querySelectorAll(selector)` — disambiguates a
|
|
11
|
+
* component repeated within one artboard. Absent → first match. */
|
|
12
|
+
index?: number;
|
|
11
13
|
tag: string;
|
|
12
14
|
classes: string;
|
|
13
15
|
text: string;
|
|
@@ -140,6 +142,7 @@ export function createInspect(
|
|
|
140
142
|
return {
|
|
141
143
|
file,
|
|
142
144
|
selector: String(sel.selector || ''),
|
|
145
|
+
index: typeof sel.index === 'number' ? sel.index : undefined,
|
|
143
146
|
tag: String(sel.tag || ''),
|
|
144
147
|
classes: String(sel.classes || ''),
|
|
145
148
|
text: String(sel.text || '').slice(0, 240),
|
|
@@ -281,7 +284,7 @@ const INSPECTOR_SCRIPT = `
|
|
|
281
284
|
var withSelector = commentsCache.filter(function(c) { return c && c.selector; });
|
|
282
285
|
withSelector.forEach(function(c, i) {
|
|
283
286
|
var target = null;
|
|
284
|
-
try {
|
|
287
|
+
try { var _all = document.querySelectorAll(c.selector); var _i = (typeof c.index === 'number' && c.index > 0 && c.index < _all.length) ? c.index : 0; target = _all[_i] || _all[0] || null; } catch (e) {}
|
|
285
288
|
var pin = document.createElement('button');
|
|
286
289
|
pin.className = 'dgn-pin' + (c.status === 'resolved' ? ' resolved' : '') + (c.id === focusedPinId ? ' focused' : '');
|
|
287
290
|
pin.textContent = String(i + 1);
|
|
@@ -309,7 +312,7 @@ const INSPECTOR_SCRIPT = `
|
|
|
309
312
|
var node = pinNodes[i];
|
|
310
313
|
var x, y, hidden = false;
|
|
311
314
|
if (!node.target || !node.target.isConnected) {
|
|
312
|
-
try { node.
|
|
315
|
+
try { var _nc = node.comment; var _na = document.querySelectorAll(_nc.selector); var _ni = (typeof _nc.index === 'number' && _nc.index > 0 && _nc.index < _na.length) ? _nc.index : 0; node.target = _na[_ni] || _na[0] || null; } catch (e) {}
|
|
313
316
|
}
|
|
314
317
|
if (node.target) {
|
|
315
318
|
var r = node.target.getBoundingClientRect();
|
|
@@ -352,6 +355,34 @@ const INSPECTOR_SCRIPT = `
|
|
|
352
355
|
new MutationObserver(function(){ startTick(); }).observe(document.documentElement, { subtree: true, attributes: true, attributeFilter: ['style', 'class'], childList: true });
|
|
353
356
|
}
|
|
354
357
|
|
|
358
|
+
// Shell chords — keydown fired inside the canvas iframe never reaches the
|
|
359
|
+
// shell's window-scoped listener (iframe keyboard isolation), so each shell
|
|
360
|
+
// chord must be forwarded to the parent. preventDefault is load-bearing for
|
|
361
|
+
// ⌘R: without it the BROWSER reloads the whole shell while focus is in the
|
|
362
|
+
// canvas (the advertised behavior is "reload the active canvas"). Capture
|
|
363
|
+
// phase so canvas-lib's pan/zoom keydown handler can't swallow them first.
|
|
364
|
+
document.addEventListener('keydown', function(e) {
|
|
365
|
+
if (!(e.metaKey || e.ctrlKey)) return;
|
|
366
|
+
var k = (e.key || '').toLowerCase();
|
|
367
|
+
if (k === 'k' && !e.shiftKey) {
|
|
368
|
+
e.preventDefault();
|
|
369
|
+
try { window.parent.postMessage({ dgn: 'toggle-palette' }, '*'); } catch (err) {}
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (k === 'r' && !e.shiftKey) {
|
|
373
|
+
e.preventDefault();
|
|
374
|
+
try { window.parent.postMessage({ dgn: 'shell-shortcut', id: 'reload' }, '*'); } catch (err) {}
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (e.shiftKey) {
|
|
378
|
+
var id = k === 'i' ? 'inspector' : k === 'm' ? 'comments' : k === 'e' ? 'export' : k === 'h' ? 'handoff' : null;
|
|
379
|
+
if (id) {
|
|
380
|
+
e.preventDefault();
|
|
381
|
+
try { window.parent.postMessage({ dgn: 'shell-shortcut', id: id }, '*'); } catch (err) {}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}, true);
|
|
385
|
+
|
|
355
386
|
window.addEventListener('message', function(e) {
|
|
356
387
|
var m = e.data;
|
|
357
388
|
if (!m || typeof m !== 'object' || !m.dgn) return;
|
|
@@ -368,10 +399,25 @@ const INSPECTOR_SCRIPT = `
|
|
|
368
399
|
var c = commentsCache.find(function(x){ return x && x.id === m.id; });
|
|
369
400
|
if (c && c.selector) {
|
|
370
401
|
try {
|
|
371
|
-
var
|
|
402
|
+
var _ta = document.querySelectorAll(c.selector); var _ti = (typeof c.index === 'number' && c.index > 0 && c.index < _ta.length) ? c.index : 0; var t = _ta[_ti] || _ta[0] || null;
|
|
372
403
|
if (t) t.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
373
404
|
} catch (e) {}
|
|
374
405
|
}
|
|
406
|
+
} else if (m.dgn === 'theme') {
|
|
407
|
+
// Catch the chrome-theme seed the shell posts in reply to our 'loaded'
|
|
408
|
+
// ping (sent below). THIS listener registers during page-load — before
|
|
409
|
+
// the React canvas-shell mounts and adds its OWN 'dgn:theme' listener —
|
|
410
|
+
// so without this branch the shell's reply lands in the gap between
|
|
411
|
+
// page-load and React-mount and is dropped, leaving the canvas stuck on
|
|
412
|
+
// its hardcoded dark default until a manual theme toggle re-broadcasts
|
|
413
|
+
// it (the recurring "open a canvas under a light shell → canvas chrome is
|
|
414
|
+
// dark; toggle twice to fix; next canvas reverts" bug). We set the same
|
|
415
|
+
// attribute the canvas-shell chrome reads (data-maude-theme on <html>);
|
|
416
|
+
// canvas-shell's default-dark guard then sees the value already present
|
|
417
|
+
// and won't override it, and its own listener takes over live toggles.
|
|
418
|
+
if (m.theme === 'light' || m.theme === 'dark') {
|
|
419
|
+
try { document.documentElement.setAttribute('data-maude-theme', m.theme); } catch (e) {}
|
|
420
|
+
}
|
|
375
421
|
}
|
|
376
422
|
/* force-clear is now consumed by canvas-shell.tsx — the inspector
|
|
377
423
|
overlay has no per-element selection state to clear anymore. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file marquee-overlay.tsx — T26 (Wave 3)
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/marquee-overlay.tsx
|
|
4
4
|
* @purpose Element-level marquee selection. Drag from empty padding inside
|
|
5
5
|
* an artboard body (NOT on top of a `[data-cd-id]` element) to
|
|
6
6
|
* lasso multiple stamped elements. Aseprite modifier vocabulary:
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import { useEffect, useRef } from 'react';
|
|
24
24
|
|
|
25
|
+
import { scopedCdSelector, selectorIndex } from './dom-selection.ts';
|
|
25
26
|
import { DRAG_THRESHOLD_PX } from './input-router.tsx';
|
|
26
27
|
import { type Selection, useSelectionSet } from './use-selection-set.tsx';
|
|
27
28
|
import { useToolMode } from './use-tool-mode.tsx';
|
|
@@ -103,10 +104,12 @@ function elementToSelection(el: Element, bodyAncestor: Element): Selection | nul
|
|
|
103
104
|
const artboardEl = bodyAncestor.closest('[data-dc-screen]');
|
|
104
105
|
const artboardId = artboardEl?.getAttribute('data-dc-screen') ?? null;
|
|
105
106
|
const rect = (el as HTMLElement).getBoundingClientRect();
|
|
107
|
+
const marqueeSelector = scopedCdSelector(cdId, artboardId);
|
|
106
108
|
return {
|
|
107
109
|
id: cdId,
|
|
108
|
-
selector:
|
|
110
|
+
selector: marqueeSelector,
|
|
109
111
|
artboardId,
|
|
112
|
+
index: selectorIndex(document, marqueeSelector, el),
|
|
110
113
|
tag: el.tagName.toLowerCase(),
|
|
111
114
|
classes: (el.getAttribute('class') ?? '')
|
|
112
115
|
.trim()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file participants-chrome.tsx — top-right avatar stack of live peers
|
|
3
|
-
* @scope
|
|
3
|
+
* @scope apps/studio/participants-chrome.tsx
|
|
4
4
|
* @purpose Phase 8 Task 6 — show who's currently on the canvas. Colored
|
|
5
5
|
* initials in a circle per peer, with a popover for full name +
|
|
6
6
|
* "Follow" button. Follow mode publishes `followTarget` on my
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* own avatar to release. tldraw-style.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { useCallback, useEffect,
|
|
17
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
18
18
|
|
|
19
19
|
import { useViewportControllerContext } from './canvas-lib.tsx';
|
|
20
20
|
import { type AgentPresence, useAgentPresence } from './use-agent-presence.tsx';
|
|
@@ -192,7 +192,6 @@ function Avatar({ peer, isFollowing, onToggleFollow }: AvatarProps): JSX.Element
|
|
|
192
192
|
setOpen((v) => !v);
|
|
193
193
|
}
|
|
194
194
|
}}
|
|
195
|
-
// biome-ignore lint/a11y/useSemanticElements: 28px round chip needs raw <div> + inline bg + custom focus ring; <button> reset is more code than the rule saves.
|
|
196
195
|
role="button"
|
|
197
196
|
tabIndex={0}
|
|
198
197
|
title={peer.name}
|
|
@@ -201,8 +200,6 @@ function Avatar({ peer, isFollowing, onToggleFollow }: AvatarProps): JSX.Element
|
|
|
201
200
|
>
|
|
202
201
|
{initialsFor(peer.name)}
|
|
203
202
|
{open && (
|
|
204
|
-
// biome-ignore lint/a11y/useKeyWithClickEvents: stop-propagation wrapper; keyboard focus stays in the button child.
|
|
205
|
-
// biome-ignore lint/a11y/useSemanticElements: popover wrapper carries no semantic role; the inner <button> is the actionable element.
|
|
206
203
|
<div className="dc-participant-popover" onClick={(e) => e.stopPropagation()}>
|
|
207
204
|
<div className="dc-participant-popover__name">{peer.name}</div>
|
|
208
205
|
<button
|
|
@@ -250,7 +247,6 @@ function AgentAvatar({ agent }: { agent: AgentPresence }): JSX.Element {
|
|
|
250
247
|
setOpen((v) => !v);
|
|
251
248
|
}
|
|
252
249
|
}}
|
|
253
|
-
// biome-ignore lint/a11y/useSemanticElements: matches the peer Avatar chip pattern.
|
|
254
250
|
role="button"
|
|
255
251
|
tabIndex={0}
|
|
256
252
|
title={`${agent.name} (AI agent)`}
|
|
@@ -262,8 +258,6 @@ function AgentAvatar({ agent }: { agent: AgentPresence }): JSX.Element {
|
|
|
262
258
|
✦
|
|
263
259
|
</span>
|
|
264
260
|
{open && (
|
|
265
|
-
// biome-ignore lint/a11y/useKeyWithClickEvents: stop-propagation wrapper; no actionable child.
|
|
266
|
-
// biome-ignore lint/a11y/useSemanticElements: popover wrapper carries no semantic role.
|
|
267
261
|
<div className="dc-participant-popover" onClick={(e) => e.stopPropagation()}>
|
|
268
262
|
<div className="dc-participant-popover__name">{agent.name}</div>
|
|
269
263
|
<div className="dc-participant-popover__sub">AI agent · {agent.author}</div>
|
|
@@ -8,10 +8,14 @@
|
|
|
8
8
|
// is the virtual `/$bunfs/root` (bun --compile
|
|
9
9
|
// embedded fs) — NOT a real disk path.
|
|
10
10
|
// Walk up from process.execPath until we find
|
|
11
|
-
// the real
|
|
12
|
-
// 3. Compiled binary, marketplace cache:
|
|
13
|
-
//
|
|
14
|
-
// dev-server
|
|
11
|
+
// the real apps/studio/ dir.
|
|
12
|
+
// 3. Compiled binary, marketplace cache: the marketplace clone ships only the
|
|
13
|
+
// plugin markdown (plugins/design/) — the
|
|
14
|
+
// dev-server ships via npm (package.json files),
|
|
15
|
+
// so the runtime is resolved from the npm
|
|
16
|
+
// package layout, never the marketplace cache.
|
|
17
|
+
// Same walk-up logic anchors on http.ts. See
|
|
18
|
+
// DDR-095 for the apps/studio relocation.
|
|
15
19
|
//
|
|
16
20
|
// Why this matters: Phase 19 v0.18.0 used `dirname(fileURLToPath(import.meta.url))`
|
|
17
21
|
// universally. In the compiled binary that's `/$bunfs/root` — a virtual path —
|
|
@@ -28,7 +32,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
28
32
|
|
|
29
33
|
/**
|
|
30
34
|
* Real disk path to the dev-server install dir
|
|
31
|
-
* (`
|
|
35
|
+
* (`apps/studio/` inside whatever package layout we're in).
|
|
32
36
|
*
|
|
33
37
|
* Always a directory that contains `http.ts` + `dist/` + (optionally)
|
|
34
38
|
* `node_modules/` and `client/`. Never a virtual `/$bunfs/*` path.
|
|
@@ -86,16 +90,16 @@ function resolveDevServerRoot(): string {
|
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
// (2 + 3) Compiled binary: walk up from process.execPath until we find a dir
|
|
89
|
-
// that *contains* `
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
+
// that *contains* `apps/studio/<canonical files>`. Match the npm install
|
|
94
|
+
// layout (binary at @1agh/maude-<plat>/maude → walk up to @1agh/maude/, which
|
|
95
|
+
// ships apps/studio/ via package.json files). The dev-server is NOT shipped via
|
|
96
|
+
// the marketplace clone, so there is no marketplace-cache anchor (DDR-095).
|
|
93
97
|
let cur = dirname(process.execPath);
|
|
94
98
|
for (let i = 0; i < 10; i++) {
|
|
95
99
|
// Check if cur itself is the dev-server root.
|
|
96
100
|
if (isDevServerDir(cur)) return cur;
|
|
97
|
-
// Check if cur contains
|
|
98
|
-
const nested = join(cur, '
|
|
101
|
+
// Check if cur contains apps/studio/.
|
|
102
|
+
const nested = join(cur, 'apps', 'studio');
|
|
99
103
|
if (isDevServerDir(nested)) return nested;
|
|
100
104
|
const parent = dirname(cur);
|
|
101
105
|
if (parent === cur) break;
|
|
@@ -101,11 +101,20 @@ const { port: BASE_PORT, explicit: PORT_EXPLICIT } = resolvePort();
|
|
|
101
101
|
|
|
102
102
|
type BunServer = ReturnType<typeof Bun.serve<WsData, never>>;
|
|
103
103
|
|
|
104
|
+
// Phase 23 security review (DDR-088 follow-up) — hard ceiling on a buffered
|
|
105
|
+
// request body. Without it Bun's 128 MB default applies, so `POST /_api/asset`
|
|
106
|
+
// would buffer up to 128 MB into RAM BEFORE api.saveAsset's 10 MB check runs
|
|
107
|
+
// (memory amplification from the untrusted canvas origin). 16 MB covers every
|
|
108
|
+
// legit body (10 MB asset + 1 MB annotation SVG + small JSON) with headroom and
|
|
109
|
+
// bounds the pre-handler buffer; the authoritative per-route caps still apply.
|
|
110
|
+
const MAX_REQUEST_BODY = 16 * 1024 * 1024;
|
|
111
|
+
|
|
104
112
|
function startServer(port: number): BunServer {
|
|
105
113
|
return Bun.serve<WsData, never>({
|
|
106
114
|
port,
|
|
107
115
|
hostname: '127.0.0.1',
|
|
108
116
|
development: process.env.NODE_ENV !== 'production',
|
|
117
|
+
maxRequestBodySize: MAX_REQUEST_BODY,
|
|
109
118
|
routes: http.routes,
|
|
110
119
|
async fetch(req, srv) {
|
|
111
120
|
const pathname = new URL(req.url).pathname;
|
|
@@ -167,6 +176,9 @@ function startCanvasServer(port: number): BunServer {
|
|
|
167
176
|
port,
|
|
168
177
|
hostname: '127.0.0.1',
|
|
169
178
|
development: process.env.NODE_ENV !== 'production',
|
|
179
|
+
// DDR-088 follow-up — bound the pre-handler request buffer on the UNTRUSTED
|
|
180
|
+
// canvas origin (the asset upload lives here). See MAX_REQUEST_BODY.
|
|
181
|
+
maxRequestBodySize: MAX_REQUEST_BODY,
|
|
170
182
|
// Hard allowlist of route-table endpoints (Bun matches `routes` before
|
|
171
183
|
// `fetch`). Only the collab/display-data endpoints the canvas runtime needs
|
|
172
184
|
// — see http.isCanvasSafeRoute for the trust rationale. The dynamic
|
|
@@ -176,6 +188,12 @@ function startCanvasServer(port: number): BunServer {
|
|
|
176
188
|
'/_api/git-user': http.routes['/_api/git-user'],
|
|
177
189
|
'/_api/canvas-meta': http.routes['/_api/canvas-meta'],
|
|
178
190
|
'/_api/annotations': http.routes['/_api/annotations'],
|
|
191
|
+
// Phase 23 — capped binary image upload (magic-byte sniff + 10 MB +
|
|
192
|
+
// content-addressed name + traversal guard + no-SVG, in api.saveAsset).
|
|
193
|
+
// Bun matches `routes` BEFORE `fetch`, so the route must be listed here
|
|
194
|
+
// explicitly — the CANVAS_SAFE_API entry alone only opens the fetch
|
|
195
|
+
// fall-through (which serves files, not route handlers). See DDR (Task 9).
|
|
196
|
+
'/_api/asset': http.routes['/_api/asset'],
|
|
179
197
|
'/_api/git-committers': http.routes['/_api/git-committers'],
|
|
180
198
|
'/_api/ai': http.routes['/_api/ai'],
|
|
181
199
|
'/_comments': http.routes['/_comments'],
|
|
@@ -276,8 +294,11 @@ let server: BunServer;
|
|
|
276
294
|
|
|
277
295
|
// T2 (9.1-A) — advertise the main origin so the canvas origin's CSP can
|
|
278
296
|
// allowlist it in `frame-ancestors` (the legit embedder). Must be set before
|
|
279
|
-
// the canvas listener serves its first shell.
|
|
280
|
-
|
|
297
|
+
// the canvas listener serves its first shell. Both loopback host spellings are
|
|
298
|
+
// listed: the server binds 127.0.0.1, so a user who opens the printed URL as
|
|
299
|
+
// `127.0.0.1:<port>` is the same legit embedder — with only `localhost` allowed
|
|
300
|
+
// the canvas iframe was silently refused (blank sad-page, no error anywhere).
|
|
301
|
+
ctx.mainOrigin = `http://localhost:${server.port} http://127.0.0.1:${server.port}`;
|
|
281
302
|
|
|
282
303
|
// T2 (9.1-A) — segregated canvas-content origin. ON BY DEFAULT (opt-OUT) since
|
|
283
304
|
// phase-9.1: a second listener binds an OS-assigned free port, advertised as
|