@1agh/maude 1.0.11 → 1.2.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/apps/studio/acp/bridge.ts +18 -13
- package/apps/studio/acp/transcript-io.ts +394 -0
- package/apps/studio/acp/transcript.ts +103 -38
- package/apps/studio/api.ts +139 -26
- package/apps/studio/bin/_import-figma.mjs +57 -11
- package/apps/studio/bin/_seed-fixture.mjs +163 -0
- package/apps/studio/canvas-imports.ts +89 -0
- package/apps/studio/client/app.jsx +257 -7
- package/apps/studio/client/export-center.jsx +15 -7
- package/apps/studio/client/panels/SyncPanel.jsx +125 -4
- package/apps/studio/client/report-bug.jsx +19 -1
- package/apps/studio/client/styles/3-shell-maude.css +34 -0
- package/apps/studio/client/styles/4-components.css +26 -0
- package/apps/studio/collab/persistence.ts +132 -16
- package/apps/studio/collab/registry.ts +10 -18
- package/apps/studio/debug-bundle.ts +13 -0
- package/apps/studio/dist/client.bundle.js +1128 -1128
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/export-dialog.tsx +54 -0
- package/apps/studio/exporters/degraded.ts +19 -3
- package/apps/studio/exporters/ghostscript.ts +194 -0
- package/apps/studio/exporters/pdf-fonts.ts +261 -0
- package/apps/studio/exporters/pdf.ts +152 -3
- package/apps/studio/exporters/remote.ts +36 -1
- package/apps/studio/fs-watch.ts +45 -1
- package/apps/studio/http.ts +7 -1
- package/apps/studio/mem.ts +45 -9
- package/apps/studio/server.ts +11 -4
- package/apps/studio/sync/agent.ts +136 -29
- package/apps/studio/sync/asset-push.ts +1 -47
- package/apps/studio/sync/codec.ts +122 -31
- package/apps/studio/sync/cold-start-apply.ts +7 -0
- package/apps/studio/sync/cold-start.ts +118 -22
- package/apps/studio/sync/comment-identity.ts +68 -0
- package/apps/studio/sync/connection-state.ts +15 -1
- package/apps/studio/sync/file-ledger.ts +117 -0
- package/apps/studio/sync/file-plane.ts +623 -27
- package/apps/studio/sync/index.ts +711 -20
- package/apps/studio/sync/journal-client.ts +13 -1
- package/apps/studio/sync/migrate-seed.ts +40 -3
- package/apps/studio/sync/presentation.ts +26 -3
- package/apps/studio/sync/retry-after.ts +169 -0
- package/apps/studio/sync/seed-progress.ts +218 -0
- package/apps/studio/sync/status.ts +122 -5
- package/apps/studio/sync/transport-error.ts +111 -0
- package/apps/studio/whats-new.json +72 -0
- package/cli/bin/maude.mjs +2 -0
- package/cli/commands/codex.mjs +191 -0
- package/cli/commands/harness.mjs +1051 -0
- package/cli/commands/help.mjs +13 -0
- package/cli/lib/design-link.mjs +190 -2
- package/cli/lib/harness/capabilities.mjs +302 -0
- package/cli/lib/harness/codex-runtime.mjs +889 -0
- package/cli/lib/harness/compatibility.mjs +27 -0
- package/cli/lib/harness/discover-claude.mjs +1393 -0
- package/cli/lib/harness/managed-state.mjs +1296 -0
- package/cli/lib/harness/model.mjs +86 -0
- package/cli/lib/harness/secrets.mjs +390 -0
- package/cli/lib/harness/targets/codex.mjs +1141 -0
- package/cli/lib/harness/targets/opencode.mjs +807 -0
- package/cli/lib/harness/transaction.mjs +1099 -0
- package/cli/lib/hubs-config.mjs +35 -8
- package/cli/templates/harness/opencode/maude-projector.ts +58 -0
- package/docs/harness-capability-matrix.md +125 -0
- package/docs/harness-environment-projection.md +95 -0
- package/package.json +32 -9
- package/plugins/flow/.claude-plugin/config.schema.json +8 -0
- package/apps/studio/.ai/cache/_stats.json +0 -7
- package/apps/studio/bin/_agent-browser-safe.test.mjs +0 -165
- package/apps/studio/bin/_curl-local.test.mjs +0 -280
- package/apps/studio/bin/_pw-launch.test.mjs +0 -90
- package/apps/studio/bin/_smart-frames.test.mjs +0 -195
- package/apps/studio/bin/_transcribe.test.mjs +0 -80
- package/apps/studio/bun.lock +0 -928
- package/apps/studio/draw/test/animate.test.ts +0 -172
- package/apps/studio/draw/test/brush.test.ts +0 -213
- package/apps/studio/draw/test/composition.test.ts +0 -141
- package/apps/studio/draw/test/geometry.test.ts +0 -199
- package/apps/studio/draw/test/gradient.test.ts +0 -167
- package/apps/studio/draw/test/layout.test.ts +0 -120
- package/apps/studio/draw/test/morph.test.ts +0 -107
- package/apps/studio/draw/test/optimize.test.ts +0 -40
- package/apps/studio/draw/test/palette.test.ts +0 -123
- package/apps/studio/draw/test/primitives.test.ts +0 -129
- package/apps/studio/draw/test/serialize-animate.test.ts +0 -142
- package/apps/studio/draw/test/serialize.test.ts +0 -105
- package/apps/studio/figma/assets.test.ts +0 -464
- package/apps/studio/figma/client.test.ts +0 -395
- package/apps/studio/figma/codegen-client.test.ts +0 -276
- package/apps/studio/figma/codegen-fonts.test.ts +0 -103
- package/apps/studio/figma/codegen-values.test.ts +0 -179
- package/apps/studio/figma/comments-to-strokes.test.ts +0 -194
- package/apps/studio/figma/fig-decode.test.ts +0 -788
- package/apps/studio/figma/fig-differential.test.ts +0 -182
- package/apps/studio/figma/fig-translator.test.ts +0 -192
- package/apps/studio/figma/fig-vector.test.ts +0 -113
- package/apps/studio/figma/from-codegen.test.ts +0 -408
- package/apps/studio/figma/sanitize.test.ts +0 -325
- package/apps/studio/figma/tailwind-map.test.ts +0 -142
- package/apps/studio/figma/to-artboard.test.ts +0 -808
- package/apps/studio/figma/to-render.test.ts +0 -180
- package/apps/studio/figma/to-strokes-roundtrip.test.ts +0 -152
- package/apps/studio/figma/to-strokes.test.ts +0 -705
- package/apps/studio/figma/to-tokens.test.ts +0 -321
- package/apps/studio/figma/url.test.ts +0 -167
- package/apps/studio/footage/schema.test.ts +0 -287
- package/apps/studio/generation/adapters/groq.test.ts +0 -80
- package/apps/studio/generation/audio-library.test.ts +0 -95
- package/apps/studio/generation/captions.test.ts +0 -78
- package/apps/studio/generation/elevenlabs.test.ts +0 -223
- package/apps/studio/generation/gemini.test.ts +0 -350
- package/apps/studio/generation/jobs.test.ts +0 -128
- package/apps/studio/generation/keys.test.ts +0 -75
- package/apps/studio/generation/prefs.test.ts +0 -95
- package/apps/studio/generation/types.test.ts +0 -123
- package/apps/studio/generation/whisper-models.test.ts +0 -179
- package/apps/studio/test/_helpers.ts +0 -108
- package/apps/studio/test/acp-activity-endpoint.test.ts +0 -183
- package/apps/studio/test/acp-activity.test.ts +0 -154
- package/apps/studio/test/acp-ai-activity.test.ts +0 -182
- package/apps/studio/test/acp-attachment-serve.test.ts +0 -74
- package/apps/studio/test/acp-bootstrap-brief.test.ts +0 -178
- package/apps/studio/test/acp-branch-guard.test.ts +0 -123
- package/apps/studio/test/acp-bridge-lifetime.test.ts +0 -475
- package/apps/studio/test/acp-bridge.test.ts +0 -545
- package/apps/studio/test/acp-capabilities.test.ts +0 -123
- package/apps/studio/test/acp-caps-bridge.test.ts +0 -279
- package/apps/studio/test/acp-commands.test.ts +0 -119
- package/apps/studio/test/acp-elicitation-bridge.test.ts +0 -480
- package/apps/studio/test/acp-elicitation.test.ts +0 -251
- package/apps/studio/test/acp-env.test.ts +0 -95
- package/apps/studio/test/acp-mode-banner.test.ts +0 -45
- package/apps/studio/test/acp-origin-gate.test.ts +0 -160
- package/apps/studio/test/acp-permission-prompt.test.ts +0 -251
- package/apps/studio/test/acp-permission.test.ts +0 -278
- package/apps/studio/test/acp-plugin-bootstrap.test.ts +0 -89
- package/apps/studio/test/acp-session-allowed-tools.test.ts +0 -235
- package/apps/studio/test/acp-session-plugins.test.ts +0 -209
- package/apps/studio/test/acp-toolgroup.test.ts +0 -76
- package/apps/studio/test/acp-transcript-view.test.ts +0 -71
- package/apps/studio/test/acp-transcript.test.ts +0 -238
- package/apps/studio/test/acp-usage-bridge.test.ts +0 -141
- package/apps/studio/test/acp-usage.test.ts +0 -143
- package/apps/studio/test/acp-write-gate.test.ts +0 -323
- package/apps/studio/test/acp-write-scope.test.ts +0 -533
- package/apps/studio/test/active-state.test.ts +0 -186
- package/apps/studio/test/activity.test.ts +0 -189
- package/apps/studio/test/ai-activity.test.ts +0 -113
- package/apps/studio/test/annotate-write.test.ts +0 -1074
- package/apps/studio/test/annotation-strokes-command.test.ts +0 -166
- package/apps/studio/test/annotations-align.test.ts +0 -88
- package/apps/studio/test/annotations-api.test.ts +0 -145
- package/apps/studio/test/annotations-bindings.test.ts +0 -262
- package/apps/studio/test/annotations-draw-modifiers.test.ts +0 -206
- package/apps/studio/test/annotations-groups.test.ts +0 -231
- package/apps/studio/test/annotations-layer.test.ts +0 -1011
- package/apps/studio/test/annotations-roundtrip.test.ts +0 -663
- package/apps/studio/test/annotations-snap.test.ts +0 -135
- package/apps/studio/test/annotations-text-format.test.ts +0 -396
- package/apps/studio/test/artboard-activity-overlay.test.tsx +0 -56
- package/apps/studio/test/artboard-guides-overlay.test.tsx +0 -152
- package/apps/studio/test/artboard-hug-commit.test.ts +0 -36
- package/apps/studio/test/artboard-kinds.test.tsx +0 -83
- package/apps/studio/test/artboard-selection-attrs.test.ts +0 -69
- package/apps/studio/test/asset-api.test.ts +0 -149
- package/apps/studio/test/assets-s3.test.ts +0 -249
- package/apps/studio/test/binary-smoke.test.ts +0 -47
- package/apps/studio/test/boot-self-heal.test.ts +0 -87
- package/apps/studio/test/browse-posture.test.tsx +0 -107
- package/apps/studio/test/bundle-smoke.test.ts +0 -46
- package/apps/studio/test/camera-reveal.test.tsx +0 -173
- package/apps/studio/test/canvas-artifacts.test.ts +0 -125
- package/apps/studio/test/canvas-build-sandbox.test.ts +0 -69
- package/apps/studio/test/canvas-build.test.ts +0 -141
- package/apps/studio/test/canvas-create-api.test.ts +0 -676
- package/apps/studio/test/canvas-cursors.test.ts +0 -164
- package/apps/studio/test/canvas-edit.test.ts +0 -470
- package/apps/studio/test/canvas-freshness-deps.test.ts +0 -64
- package/apps/studio/test/canvas-header.test.ts +0 -127
- package/apps/studio/test/canvas-hide-chrome.test.ts +0 -88
- package/apps/studio/test/canvas-hmr-runtime.test.tsx +0 -114
- package/apps/studio/test/canvas-lib-inline.test.ts +0 -146
- package/apps/studio/test/canvas-lib-motion.test.ts +0 -131
- package/apps/studio/test/canvas-lib-resolver.test.ts +0 -111
- package/apps/studio/test/canvas-list-watch.test.ts +0 -388
- package/apps/studio/test/canvas-media-drop.test.ts +0 -187
- package/apps/studio/test/canvas-meta-api.test.ts +0 -707
- package/apps/studio/test/canvas-move-api.test.ts +0 -587
- package/apps/studio/test/canvas-origin-gate.test.ts +0 -417
- package/apps/studio/test/canvas-pipeline.test.ts +0 -180
- package/apps/studio/test/canvas-rects.test.ts +0 -198
- package/apps/studio/test/canvas-reorder.test.ts +0 -211
- package/apps/studio/test/canvas-route.test.ts +0 -179
- package/apps/studio/test/canvas-shell-base.test.ts +0 -107
- package/apps/studio/test/canvas-shell-build-error.test.ts +0 -52
- package/apps/studio/test/canvas-url.test.ts +0 -138
- package/apps/studio/test/canvas-wheel-delta.test.ts +0 -42
- package/apps/studio/test/canvas-zoom-floor.test.ts +0 -73
- package/apps/studio/test/capture-core.test.ts +0 -32
- package/apps/studio/test/capture-determinism-shape.test.ts +0 -135
- package/apps/studio/test/chat-attachments.test.ts +0 -117
- package/apps/studio/test/chat-context.test.ts +0 -129
- package/apps/studio/test/chat-markdown.test.tsx +0 -58
- package/apps/studio/test/clip-addressing.test.ts +0 -738
- package/apps/studio/test/clip-ops.test.ts +0 -922
- package/apps/studio/test/cloud-build.test.ts +0 -95
- package/apps/studio/test/cloud-connect-note.test.ts +0 -135
- package/apps/studio/test/cloud-endpoints.test.ts +0 -590
- package/apps/studio/test/cloud-history-hardening.test.ts +0 -165
- package/apps/studio/test/cloud-history-posture.test.ts +0 -230
- package/apps/studio/test/cloud-managed-save-surfaces.test.ts +0 -90
- package/apps/studio/test/cloud-renew.test.ts +0 -205
- package/apps/studio/test/cloud-session-role.test.ts +0 -161
- package/apps/studio/test/cloud-shell-surfaces.test.ts +0 -170
- package/apps/studio/test/cold-start-apply.test.ts +0 -303
- package/apps/studio/test/collab-annotations-bridge.test.ts +0 -55
- package/apps/studio/test/collab-awareness-bridge.test.ts +0 -223
- package/apps/studio/test/collab-bridge.test.ts +0 -79
- package/apps/studio/test/collab-loopback.test.ts +0 -63
- package/apps/studio/test/collab-origin-gate.test.ts +0 -323
- package/apps/studio/test/collab-protocol.test.ts +0 -146
- package/apps/studio/test/collab-readonly-gate.test.ts +0 -194
- package/apps/studio/test/collab-reseed-guard.test.ts +0 -78
- package/apps/studio/test/collab-room.test.ts +0 -188
- package/apps/studio/test/collab-session-survival.test.tsx +0 -176
- package/apps/studio/test/collab-stress.test.ts +0 -129
- package/apps/studio/test/comment-mount.test.ts +0 -97
- package/apps/studio/test/comment-relay-origin-gate.test.ts +0 -117
- package/apps/studio/test/comments-api.test.ts +0 -277
- package/apps/studio/test/comments-fs-rebroadcast.test.ts +0 -155
- package/apps/studio/test/comments-overlay.test.ts +0 -117
- package/apps/studio/test/compile-entry.test.ts +0 -133
- package/apps/studio/test/component-map.test.ts +0 -48
- package/apps/studio/test/config-projection.test.ts +0 -104
- package/apps/studio/test/config-reload.test.ts +0 -230
- package/apps/studio/test/config-version.test.ts +0 -88
- package/apps/studio/test/context-resolve-tokens.test.ts +0 -97
- package/apps/studio/test/convert-to-absolute.test.ts +0 -333
- package/apps/studio/test/credential-grammar.test.ts +0 -107
- package/apps/studio/test/csp-canvas-shell.test.ts +0 -62
- package/apps/studio/test/csrf-write-guard.test.ts +0 -141
- package/apps/studio/test/debug-bundle.test.ts +0 -105
- package/apps/studio/test/design-setup-readiness.test.ts +0 -155
- package/apps/studio/test/detach-component.test.ts +0 -94
- package/apps/studio/test/dns-rebinding-guard.test.ts +0 -101
- package/apps/studio/test/dom-selection.test.ts +0 -198
- package/apps/studio/test/dynamic-text-edit.test.ts +0 -162
- package/apps/studio/test/edit-css-occurrence.test.ts +0 -81
- package/apps/studio/test/edit-persistence.test.ts +0 -91
- package/apps/studio/test/edit-scope-api.test.ts +0 -119
- package/apps/studio/test/edit-source-command.test.ts +0 -142
- package/apps/studio/test/edit-suppress.test.ts +0 -170
- package/apps/studio/test/editing-presence.test.ts +0 -103
- package/apps/studio/test/element-resize.test.ts +0 -136
- package/apps/studio/test/element-structural-api.test.ts +0 -434
- package/apps/studio/test/element-structural-edit.test.ts +0 -750
- package/apps/studio/test/ensure-browser.test.ts +0 -92
- package/apps/studio/test/equal-spacing-detector.test.ts +0 -257
- package/apps/studio/test/export-assemble.test.ts +0 -53
- package/apps/studio/test/export-browser-lane.test.ts +0 -126
- package/apps/studio/test/export-capture-fidelity.test.ts +0 -146
- package/apps/studio/test/export-capture-hygiene.test.ts +0 -675
- package/apps/studio/test/export-center.test.tsx +0 -287
- package/apps/studio/test/export-e2e-lanes.test.ts +0 -857
- package/apps/studio/test/export-format-scope-coherence.test.ts +0 -89
- package/apps/studio/test/export-lane.test.ts +0 -245
- package/apps/studio/test/export-shim-multi-capture.test.ts +0 -83
- package/apps/studio/test/exporters/canva.test.ts +0 -63
- package/apps/studio/test/exporters/degraded-propagation.test.ts +0 -123
- package/apps/studio/test/exporters/endpoint.test.ts +0 -121
- package/apps/studio/test/exporters/history.test.ts +0 -107
- package/apps/studio/test/exporters/html.test.ts +0 -26
- package/apps/studio/test/exporters/jobs.test.ts +0 -342
- package/apps/studio/test/exporters/pdf.test.ts +0 -85
- package/apps/studio/test/exporters/png.test.ts +0 -103
- package/apps/studio/test/exporters/pptx-deck.test.ts +0 -112
- package/apps/studio/test/exporters/pptx.test.ts +0 -31
- package/apps/studio/test/exporters/pw-launch.test.ts +0 -49
- package/apps/studio/test/exporters/runtime.test.ts +0 -59
- package/apps/studio/test/exporters/scope.test.ts +0 -292
- package/apps/studio/test/exporters/svg.test.ts +0 -29
- package/apps/studio/test/exporters/unsupported-media.test.ts +0 -123
- package/apps/studio/test/exporters/zip.test.ts +0 -104
- package/apps/studio/test/fetch-asset-gate.test.ts +0 -189
- package/apps/studio/test/figjam-v3-model.test.ts +0 -342
- package/apps/studio/test/figma-explode.test.ts +0 -438
- package/apps/studio/test/figma-provenance.test.ts +0 -108
- package/apps/studio/test/figma-routes.test.ts +0 -294
- package/apps/studio/test/file-lock.test.ts +0 -84
- package/apps/studio/test/fixtures/backgrounding-claude-auth.sh +0 -12
- package/apps/studio/test/fixtures/fake-claude-auth.mjs +0 -23
- package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +0 -1
- package/apps/studio/test/fixtures/mock-acp-agent-caps.mjs +0 -158
- package/apps/studio/test/fixtures/mock-acp-agent-commands.mjs +0 -44
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-flood.mjs +0 -46
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-url.mjs +0 -42
- package/apps/studio/test/fixtures/mock-acp-agent-elicit.mjs +0 -63
- package/apps/studio/test/fixtures/mock-acp-agent-permission-flood.mjs +0 -51
- package/apps/studio/test/fixtures/mock-acp-agent-permission.mjs +0 -50
- package/apps/studio/test/fixtures/mock-acp-agent-slow.mjs +0 -45
- package/apps/studio/test/fixtures/mock-acp-agent-usage.mjs +0 -56
- package/apps/studio/test/fixtures/mock-acp-agent-wedged.mjs +0 -37
- package/apps/studio/test/fixtures/mock-acp-agent-write.mjs +0 -118
- package/apps/studio/test/fixtures/mock-acp-agent.mjs +0 -69
- package/apps/studio/test/fixtures/noisy-claude-auth.mjs +0 -27
- package/apps/studio/test/fixtures/perf-canvas.mjs +0 -201
- package/apps/studio/test/fixtures/phase-20-annotations.svg +0 -1
- package/apps/studio/test/fixtures/phase-21-annotations.svg +0 -1
- package/apps/studio/test/fixtures/tiny-subset.woff2 +0 -0
- package/apps/studio/test/fixtures/video-comp-fixture.tsx +0 -82
- package/apps/studio/test/fixtures/wedged-claude-auth.sh +0 -11
- package/apps/studio/test/footage-store.test.ts +0 -100
- package/apps/studio/test/fs-mkdir-api.test.ts +0 -248
- package/apps/studio/test/fs-watch.test.ts +0 -41
- package/apps/studio/test/gemma-models.test.ts +0 -245
- package/apps/studio/test/generate-route.test.ts +0 -106
- package/apps/studio/test/git-api.test.ts +0 -0
- package/apps/studio/test/git-branches.test.ts +0 -341
- package/apps/studio/test/git-cloud-posture.test.ts +0 -59
- package/apps/studio/test/git-grouping.test.ts +0 -106
- package/apps/studio/test/git-lifecycle.test.ts +0 -100
- package/apps/studio/test/git-log-format.test.ts +0 -95
- package/apps/studio/test/git-safe-rel.test.ts +0 -132
- package/apps/studio/test/git-system-engine.test.ts +0 -135
- package/apps/studio/test/git-watch.test.ts +0 -97
- package/apps/studio/test/github-api.test.ts +0 -577
- package/apps/studio/test/grid-track-handles.test.ts +0 -160
- package/apps/studio/test/handoff-static-frames.test.ts +0 -215
- package/apps/studio/test/handoff.test.ts +0 -329
- package/apps/studio/test/history-rollback.test.ts +0 -88
- package/apps/studio/test/hmr-broadcast.test.ts +0 -236
- package/apps/studio/test/hmr-classify.test.ts +0 -87
- package/apps/studio/test/hub-link.test.ts +0 -80
- package/apps/studio/test/identity-cache.test.ts +0 -83
- package/apps/studio/test/import-asset-browser.test.ts +0 -64
- package/apps/studio/test/import-asset-route.test.ts +0 -71
- package/apps/studio/test/import-asset.test.ts +0 -427
- package/apps/studio/test/import-brand-browser.test.ts +0 -88
- package/apps/studio/test/import-brand-route.test.ts +0 -120
- package/apps/studio/test/import-brand.test.ts +0 -206
- package/apps/studio/test/import-figma.test.ts +0 -667
- package/apps/studio/test/import-tokens.test.ts +0 -722
- package/apps/studio/test/index-data-canvas-ds.test.ts +0 -97
- package/apps/studio/test/input-router.test.ts +0 -757
- package/apps/studio/test/inspect-script-syntax.test.ts +0 -53
- package/apps/studio/test/inspect-selections.test.ts +0 -219
- package/apps/studio/test/knob-props-authored.test.ts +0 -87
- package/apps/studio/test/layers-synthetic-groups.test.ts +0 -96
- package/apps/studio/test/locator.test.ts +0 -214
- package/apps/studio/test/marquee-overlay.test.ts +0 -94
- package/apps/studio/test/media-commit-chain.test.ts +0 -210
- package/apps/studio/test/mode-toggle.test.tsx +0 -226
- package/apps/studio/test/motion-sample.test.ts +0 -35
- package/apps/studio/test/move-artboards-command.test.ts +0 -108
- package/apps/studio/test/participants-chrome.test.ts +0 -65
- package/apps/studio/test/paths.test.ts +0 -88
- package/apps/studio/test/pdf-print-boxes.test.ts +0 -359
- package/apps/studio/test/peer-selection-follows-camera.test.tsx +0 -131
- package/apps/studio/test/perf-harness.ts +0 -193
- package/apps/studio/test/phase-3.6-smoke.test.ts +0 -79
- package/apps/studio/test/photo-bg-remove-validation.test.ts +0 -71
- package/apps/studio/test/photo-canvas-bundle.test.ts +0 -79
- package/apps/studio/test/photo-edit-api.test.ts +0 -201
- package/apps/studio/test/photo-filters.test.ts +0 -149
- package/apps/studio/test/photo-pipeline.test.ts +0 -106
- package/apps/studio/test/photo-taxonomy.test.ts +0 -96
- package/apps/studio/test/print-marks.test.ts +0 -113
- package/apps/studio/test/print-units.test.ts +0 -173
- package/apps/studio/test/read-annotations.test.ts +0 -727
- package/apps/studio/test/read-only-gate.test.ts +0 -188
- package/apps/studio/test/read-only-role.test.ts +0 -95
- package/apps/studio/test/readiness.test.ts +0 -337
- package/apps/studio/test/remote-ahead-behind-cache.test.ts +0 -89
- package/apps/studio/test/reorder-api.test.ts +0 -210
- package/apps/studio/test/repo-concurrency.test.ts +0 -146
- package/apps/studio/test/repo-lock.test.ts +0 -350
- package/apps/studio/test/report-proxy.test.ts +0 -125
- package/apps/studio/test/ripple.test.ts +0 -197
- package/apps/studio/test/runtime-bundle-error-mapping.test.ts +0 -43
- package/apps/studio/test/runtime-bundle.test.ts +0 -69
- package/apps/studio/test/sanitize-annotation-svg.test.ts +0 -238
- package/apps/studio/test/scaffold-design.test.ts +0 -122
- package/apps/studio/test/server-lifecycle.test.ts +0 -38
- package/apps/studio/test/session-runtime-state.test.ts +0 -226
- package/apps/studio/test/session-scope.test.ts +0 -100
- package/apps/studio/test/shared-doc-cell-pairing.test.ts +0 -642
- package/apps/studio/test/shared-doc-convergence.test.ts +0 -265
- package/apps/studio/test/shared-doc-foundation.test.ts +0 -63
- package/apps/studio/test/shared-doc-migrate.test.ts +0 -369
- package/apps/studio/test/shared-doc-projection.test.ts +0 -238
- package/apps/studio/test/shell-importmap.test.ts +0 -56
- package/apps/studio/test/sizing-mode.test.ts +0 -102
- package/apps/studio/test/slash-commands.test.ts +0 -117
- package/apps/studio/test/snap-distance-pill.test.ts +0 -68
- package/apps/studio/test/spacing-handles.test.ts +0 -138
- package/apps/studio/test/specimen-select.test.ts +0 -88
- package/apps/studio/test/sync-agent.test.ts +0 -660
- package/apps/studio/test/sync-annotations-cold-start.test.ts +0 -302
- package/apps/studio/test/sync-asset-push.test.ts +0 -931
- package/apps/studio/test/sync-atomic-write.test.ts +0 -62
- package/apps/studio/test/sync-attach-incremental.test.ts +0 -527
- package/apps/studio/test/sync-autocommit.test.ts +0 -537
- package/apps/studio/test/sync-canvas-path.test.ts +0 -200
- package/apps/studio/test/sync-cell-write-nudge.test.ts +0 -346
- package/apps/studio/test/sync-codec.test.ts +0 -230
- package/apps/studio/test/sync-cold-seed-dedup.test.ts +0 -187
- package/apps/studio/test/sync-cold-start.test.ts +0 -304
- package/apps/studio/test/sync-connect-honesty.test.ts +0 -114
- package/apps/studio/test/sync-connection-state.test.ts +0 -337
- package/apps/studio/test/sync-ctl-channel.test.ts +0 -508
- package/apps/studio/test/sync-decide-file.test.ts +0 -420
- package/apps/studio/test/sync-doc-name.test.ts +0 -281
- package/apps/studio/test/sync-echo-guard.test.ts +0 -96
- package/apps/studio/test/sync-file-ledger.test.ts +0 -334
- package/apps/studio/test/sync-file-membership.test.ts +0 -347
- package/apps/studio/test/sync-file-plane.test.ts +0 -976
- package/apps/studio/test/sync-file-pull.test.ts +0 -333
- package/apps/studio/test/sync-fresh-link-parity.test.ts +0 -267
- package/apps/studio/test/sync-fs-mirror.test.ts +0 -181
- package/apps/studio/test/sync-hardening.test.ts +0 -586
- package/apps/studio/test/sync-hub-listing.test.ts +0 -46
- package/apps/studio/test/sync-hubs-config.test.ts +0 -133
- package/apps/studio/test/sync-incident-replay.test.ts +0 -211
- package/apps/studio/test/sync-journal.test.ts +0 -176
- package/apps/studio/test/sync-meta-codec.test.ts +0 -199
- package/apps/studio/test/sync-migrate-flat-fallback.test.ts +0 -98
- package/apps/studio/test/sync-move-retirement.test.ts +0 -231
- package/apps/studio/test/sync-panel-surface.test.ts +0 -153
- package/apps/studio/test/sync-path-pull.test.ts +0 -531
- package/apps/studio/test/sync-presentation.test.ts +0 -262
- package/apps/studio/test/sync-pull-budget.test.ts +0 -169
- package/apps/studio/test/sync-remote-docs.test.ts +0 -333
- package/apps/studio/test/sync-resync-routes.test.ts +0 -168
- package/apps/studio/test/sync-runtime.test.ts +0 -1675
- package/apps/studio/test/sync-seed-defers-to-hub.test.ts +0 -83
- package/apps/studio/test/sync-settings-routes.test.ts +0 -195
- package/apps/studio/test/sync-settings.test.ts +0 -151
- package/apps/studio/test/sync-status.test.ts +0 -261
- package/apps/studio/test/sync-supervisor.test.ts +0 -262
- package/apps/studio/test/sync-tombstone-apply.test.ts +0 -111
- package/apps/studio/test/sync-trash.test.ts +0 -132
- package/apps/studio/test/sync-two-peer-discovery.test.ts +0 -343
- package/apps/studio/test/sync-untrusted.test.ts +0 -103
- package/apps/studio/test/system-endpoint.test.ts +0 -144
- package/apps/studio/test/text-editability-stamp.test.ts +0 -131
- package/apps/studio/test/timeline-comp-target.test.ts +0 -139
- package/apps/studio/test/timeline-media-cache.test.ts +0 -41
- package/apps/studio/test/timeline-parse.test.ts +0 -184
- package/apps/studio/test/timeline-scale.test.ts +0 -65
- package/apps/studio/test/timeline-snap.test.ts +0 -85
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +0 -86
- package/apps/studio/test/tool-palette.test.tsx +0 -55
- package/apps/studio/test/tour-overlay.test.tsx +0 -90
- package/apps/studio/test/trusted-request-host.test.ts +0 -66
- package/apps/studio/test/undo-sequence-byte-compare.test.ts +0 -211
- package/apps/studio/test/undo-stack.test.ts +0 -244
- package/apps/studio/test/use-agent-presence.test.tsx +0 -117
- package/apps/studio/test/use-annotation-resize.test.ts +0 -402
- package/apps/studio/test/use-annotation-selection.test.tsx +0 -77
- package/apps/studio/test/use-artboard-drag.test.ts +0 -324
- package/apps/studio/test/use-canvas-activity.test.tsx +0 -206
- package/apps/studio/test/use-collab.test.ts +0 -0
- package/apps/studio/test/use-cursor-modifiers.test.ts +0 -59
- package/apps/studio/test/use-keyboard-discipline.test.ts +0 -27
- package/apps/studio/test/use-selection-set.test.tsx +0 -166
- package/apps/studio/test/use-snap-guides.test.ts +0 -271
- package/apps/studio/test/use-tool-mode.test.tsx +0 -186
- package/apps/studio/test/use-undo-stack.test.tsx +0 -223
- package/apps/studio/test/video-asset.test.ts +0 -163
- package/apps/studio/test/video-comp-fixture.test.ts +0 -50
- package/apps/studio/test/video-comp.test.ts +0 -270
- package/apps/studio/test/video-encode-lib.test.ts +0 -63
- package/apps/studio/test/video-render-bridge.test.ts +0 -171
- package/apps/studio/test/whats-new.test.ts +0 -113
- package/apps/studio/test/whisper-setup.test.ts +0 -97
- package/apps/studio/test/workspace-containment.test.ts +0 -382
- package/apps/studio/test/workspace-signin.test.ts +0 -292
- package/apps/studio/test/ws-handshake.test.ts +0 -33
- package/cli/commands/cache.test.mjs +0 -166
- package/cli/commands/design-link.test.mjs +0 -419
- package/cli/commands/design.test.mjs +0 -240
- package/cli/commands/doctor.test.mjs +0 -237
- package/cli/commands/hub-workspace.test.mjs +0 -275
- package/cli/commands/hub.test.mjs +0 -225
- package/cli/commands/kg.test.mjs +0 -142
- package/cli/commands/preflight.test.mjs +0 -46
- package/cli/lib/argv.test.mjs +0 -46
- package/cli/lib/cache.test.mjs +0 -303
- package/cli/lib/cell-plan.test.mjs +0 -225
- package/cli/lib/config-lint.test.mjs +0 -117
- package/cli/lib/ddr-to-kgai.test.mjs +0 -118
- package/cli/lib/design-ownership.test.mjs +0 -329
- package/cli/lib/fetch-asset.test.mjs +0 -213
- package/cli/lib/figma-codegen-reachability.test.mjs +0 -104
- package/cli/lib/figma-import-controls.test.mjs +0 -70
- package/cli/lib/flow-design-integration.test.mjs +0 -165
- package/cli/lib/gitignore-block.test.mjs +0 -136
- package/cli/lib/gitignore-drift.test.mjs +0 -156
- package/cli/lib/hubs-config.test.mjs +0 -165
- package/cli/lib/pkg-root.test.mjs +0 -111
- package/cli/lib/plugin-cli-reachability.test.mjs +0 -57
- package/cli/lib/plugin-name-namespace.test.mjs +0 -71
- package/cli/lib/reconstruct-toolset.test.mjs +0 -194
- package/cli/lib/stack-detect.test.mjs +0 -121
- package/cli/lib/update-check.test.mjs +0 -32
- package/cli/lib/workspace-plan.test.mjs +0 -699
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
// Unit: pure helpers from participants-chrome.tsx.
|
|
2
|
-
|
|
3
|
-
import { describe, expect, test } from 'bun:test';
|
|
4
|
-
import { initialsFor, isOwnEditingEcho } from '../participants-chrome.tsx';
|
|
5
|
-
import type { ForeignAwareness } from '../use-collab.tsx';
|
|
6
|
-
|
|
7
|
-
const peer = (over: Partial<ForeignAwareness>): ForeignAwareness => ({
|
|
8
|
-
clientID: 1,
|
|
9
|
-
name: 'Anna',
|
|
10
|
-
color: 'oklch(0.7 0.16 145)',
|
|
11
|
-
cursor: null,
|
|
12
|
-
selection: null,
|
|
13
|
-
annotationSelection: [],
|
|
14
|
-
viewport: { x: 0, y: 0, zoom: 1 },
|
|
15
|
-
editing: null,
|
|
16
|
-
...over,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe('initialsFor', () => {
|
|
20
|
-
test('two-word name → uppercase first letters', () => {
|
|
21
|
-
expect(initialsFor('Alice Smith')).toBe('AS');
|
|
22
|
-
expect(initialsFor('michał dovrtěl')).toBe('MD');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('single-word name → first two letters uppercased', () => {
|
|
26
|
-
expect(initialsFor('alice')).toBe('AL');
|
|
27
|
-
expect(initialsFor('A')).toBe('A');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('many-word name → first letter of first two words', () => {
|
|
31
|
-
expect(initialsFor('John Ronald Reuel Tolkien')).toBe('JR');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('empty / whitespace → ?', () => {
|
|
35
|
-
expect(initialsFor('')).toBe('?');
|
|
36
|
-
expect(initialsFor(' ')).toBe('?');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('handles extra whitespace', () => {
|
|
40
|
-
expect(initialsFor(' Alice Smith ')).toBe('AS');
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('isOwnEditingEcho (Phase 30 / DDR-120)', () => {
|
|
45
|
-
test('skips MY own server-side editing echo (my name, editing, no cursor/selection)', () => {
|
|
46
|
-
expect(isOwnEditingEcho(peer({ name: 'Anna', editing: { since: 1 } }), 'Anna')).toBe(true);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('keeps a remote peer editing (different name)', () => {
|
|
50
|
-
expect(isOwnEditingEcho(peer({ name: 'Bob', editing: { since: 1 } }), 'Anna')).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('keeps MY peer if it has a live cursor (a real second tab, not the echo)', () => {
|
|
54
|
-
expect(
|
|
55
|
-
isOwnEditingEcho(
|
|
56
|
-
peer({ name: 'Anna', editing: { since: 1 }, cursor: { x: 1, y: 2 } }),
|
|
57
|
-
'Anna'
|
|
58
|
-
)
|
|
59
|
-
).toBe(false);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('keeps a peer that is not editing', () => {
|
|
63
|
-
expect(isOwnEditingEcho(peer({ name: 'Anna', editing: null }), 'Anna')).toBe(false);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
// paths.ts — resolves real disk install root across dev mode, compiled npm
|
|
2
|
-
// install, and compiled marketplace install. Phase 19.1 / v0.18.1.
|
|
3
|
-
|
|
4
|
-
import { describe, expect, test } from 'bun:test';
|
|
5
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
6
|
-
import { tmpdir } from 'node:os';
|
|
7
|
-
import { join } from 'node:path';
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
CLIENT_DIR,
|
|
11
|
-
DESIGN_PLUGIN_DIR,
|
|
12
|
-
DEV_SERVER_ROOT,
|
|
13
|
-
DIST_DIR,
|
|
14
|
-
FLOW_PLUGIN_DIR,
|
|
15
|
-
IS_COMPILED_BINARY,
|
|
16
|
-
pluginDirFrom,
|
|
17
|
-
RUNTIME_BUNDLES_DIR,
|
|
18
|
-
} from '../paths.ts';
|
|
19
|
-
|
|
20
|
-
describe('paths.ts', () => {
|
|
21
|
-
test('DEV_SERVER_ROOT contains http.ts + package.json (canonical anchor)', () => {
|
|
22
|
-
expect(DEV_SERVER_ROOT).not.toMatch(/\$bunfs|~BUN/);
|
|
23
|
-
expect(Bun.file(`${DEV_SERVER_ROOT}/http.ts`).size).toBeGreaterThan(0);
|
|
24
|
-
expect(Bun.file(`${DEV_SERVER_ROOT}/package.json`).size).toBeGreaterThan(0);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('DIST_DIR + CLIENT_DIR + RUNTIME_BUNDLES_DIR are descendants of DEV_SERVER_ROOT', () => {
|
|
28
|
-
expect(DIST_DIR).toBe(`${DEV_SERVER_ROOT}/dist`);
|
|
29
|
-
expect(CLIENT_DIR).toBe(`${DEV_SERVER_ROOT}/client`);
|
|
30
|
-
expect(RUNTIME_BUNDLES_DIR).toBe(`${DEV_SERVER_ROOT}/dist/runtime`);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('IS_COMPILED_BINARY is false when running under bun directly (this test)', () => {
|
|
34
|
-
// bun:test invokes via `bun`, so import.meta.url is a real file:// path.
|
|
35
|
-
expect(IS_COMPILED_BINARY).toBe(false);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe('pluginDirFrom — session-scoped plugin auto-bootstrap resolution (DDR-143)', () => {
|
|
40
|
-
// Both simulated layouts keep `plugins/` a sibling of `apps/studio/`, matching
|
|
41
|
-
// the dev tree and the desktop Resources bundle (stage-resources.mjs).
|
|
42
|
-
function makeLayout(shape: 'desktop-bundle' | 'npm'): { root: string; devServerRoot: string } {
|
|
43
|
-
const root = mkdtempSync(join(tmpdir(), 'maude-paths-'));
|
|
44
|
-
const devServerRoot = join(root, 'apps', 'studio');
|
|
45
|
-
mkdirSync(devServerRoot, { recursive: true });
|
|
46
|
-
for (const plugin of ['design', 'flow'] as const) {
|
|
47
|
-
const pluginRoot = join(root, 'plugins', plugin);
|
|
48
|
-
// Both layouts ship templates (DDR-044).
|
|
49
|
-
mkdirSync(join(pluginRoot, 'templates'), { recursive: true });
|
|
50
|
-
if (shape === 'desktop-bundle') {
|
|
51
|
-
// The desktop bundle stages the full loadable tree incl. the manifest.
|
|
52
|
-
mkdirSync(join(pluginRoot, '.claude-plugin'), { recursive: true });
|
|
53
|
-
mkdirSync(join(pluginRoot, 'commands'), { recursive: true });
|
|
54
|
-
writeFileSync(join(pluginRoot, '.claude-plugin', 'plugin.json'), '{"name":"x"}');
|
|
55
|
-
}
|
|
56
|
-
// npm layout: NO .claude-plugin/plugin.json (DDR-044 minimal surface).
|
|
57
|
-
}
|
|
58
|
-
return { root, devServerRoot };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
test('desktop Resources layout → resolves both plugin dirs (manifest present)', () => {
|
|
62
|
-
const { root, devServerRoot } = makeLayout('desktop-bundle');
|
|
63
|
-
try {
|
|
64
|
-
expect(pluginDirFrom(devServerRoot, 'design')).toBe(join(root, 'plugins', 'design'));
|
|
65
|
-
expect(pluginDirFrom(devServerRoot, 'flow')).toBe(join(root, 'plugins', 'flow'));
|
|
66
|
-
} finally {
|
|
67
|
-
rmSync(root, { recursive: true, force: true });
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('npm/web layout → null (templates ship, but no plugin manifest)', () => {
|
|
72
|
-
const { root, devServerRoot } = makeLayout('npm');
|
|
73
|
-
try {
|
|
74
|
-
expect(pluginDirFrom(devServerRoot, 'design')).toBeNull();
|
|
75
|
-
expect(pluginDirFrom(devServerRoot, 'flow')).toBeNull();
|
|
76
|
-
} finally {
|
|
77
|
-
rmSync(root, { recursive: true, force: true });
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test('exported constants resolve under the running dev tree (manifest present here)', () => {
|
|
82
|
-
// This suite runs from the repo dev tree, where plugins/{design,flow}/ carry
|
|
83
|
-
// their manifests — so the module-load-time constants are non-null and point
|
|
84
|
-
// at the real plugin roots.
|
|
85
|
-
expect(DESIGN_PLUGIN_DIR).toBe(join(DEV_SERVER_ROOT, '..', '..', 'plugins', 'design'));
|
|
86
|
-
expect(FLOW_PLUGIN_DIR).toBe(join(DEV_SERVER_ROOT, '..', '..', 'plugins', 'flow'));
|
|
87
|
-
});
|
|
88
|
-
});
|
|
@@ -1,359 +0,0 @@
|
|
|
1
|
-
// feature-2-print-artboards T5 — the load-bearing golden gate: MediaBox ⊇
|
|
2
|
-
// BleedBox ⊇ TrimBox with exact expected pt values, plus marks presence.
|
|
3
|
-
// Exercises pdf-lib directly (no Playwright/Chromium needed — box-setting
|
|
4
|
-
// and mark-drawing are pure pdf-lib API calls); the page size mirrors what
|
|
5
|
-
// Chromium's page.pdf() would actually produce for an artboard authored via
|
|
6
|
-
// resolvePrintArtboard (96dpi CSS px → pt, same ratio as mm → pt).
|
|
7
|
-
|
|
8
|
-
import { describe, expect, test } from 'bun:test';
|
|
9
|
-
import { PDFDocument } from 'pdf-lib';
|
|
10
|
-
|
|
11
|
-
import { readArtboardPrintProp } from '../canvas-edit.ts';
|
|
12
|
-
import {
|
|
13
|
-
applyPageFit,
|
|
14
|
-
applyPrintBoxesAndMarks,
|
|
15
|
-
artboardIdFromCssPath,
|
|
16
|
-
type PdfPrintOptions,
|
|
17
|
-
parsePageFit,
|
|
18
|
-
parsePdfPrintOptions,
|
|
19
|
-
resolvePdfDeviceScale,
|
|
20
|
-
} from '../exporters/pdf.ts';
|
|
21
|
-
import { requiredSlugPt } from '../print/marks.ts';
|
|
22
|
-
import {
|
|
23
|
-
getPaperPreset,
|
|
24
|
-
mmToPt,
|
|
25
|
-
pxToPt,
|
|
26
|
-
resolvePrintArtboard,
|
|
27
|
-
trimSizeMm,
|
|
28
|
-
} from '../print/units.ts';
|
|
29
|
-
|
|
30
|
-
const NO_MARKS: PdfPrintOptions = {
|
|
31
|
-
marks: { crop: false, registration: false, colorBars: false, pageInfo: false },
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
describe('pdf.ts — artboardIdFromCssPath', () => {
|
|
35
|
-
test('extracts the id from the artboard-by-id selector shape', () => {
|
|
36
|
-
expect(artboardIdFromCssPath('[data-dc-screen="home"]')).toBe('home');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('a descendant / :first-of-type selector yields null', () => {
|
|
40
|
-
expect(artboardIdFromCssPath('.some .descendant')).toBeNull();
|
|
41
|
-
expect(artboardIdFromCssPath('[data-dc-screen]:first-of-type')).toBeNull();
|
|
42
|
-
expect(artboardIdFromCssPath('[data-dc-screen]')).toBeNull();
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe('pdf.ts — parsePdfPrintOptions', () => {
|
|
47
|
-
test('absent/malformed → null (print post-pass off)', () => {
|
|
48
|
-
expect(parsePdfPrintOptions(undefined)).toBeNull();
|
|
49
|
-
expect(parsePdfPrintOptions(null)).toBeNull();
|
|
50
|
-
expect(parsePdfPrintOptions('nope')).toBeNull();
|
|
51
|
-
expect(parsePdfPrintOptions([1, 2])).toBeNull();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('valid shape passes through with marks defaulting false', () => {
|
|
55
|
-
expect(parsePdfPrintOptions({})).toEqual({
|
|
56
|
-
includeBleed: undefined,
|
|
57
|
-
marks: { crop: false, registration: false, colorBars: false, pageInfo: false },
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('marks flags coerce to strict booleans (non-true → false)', () => {
|
|
62
|
-
expect(parsePdfPrintOptions({ marks: { crop: 'yes', registration: 1 } })).toEqual({
|
|
63
|
-
includeBleed: undefined,
|
|
64
|
-
marks: { crop: false, registration: false, colorBars: false, pageInfo: false },
|
|
65
|
-
});
|
|
66
|
-
expect(parsePdfPrintOptions({ marks: { crop: true, registration: true } })).toEqual({
|
|
67
|
-
includeBleed: undefined,
|
|
68
|
-
marks: { crop: true, registration: true, colorBars: false, pageInfo: false },
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('includeBleed passes through only when a strict boolean', () => {
|
|
73
|
-
expect(parsePdfPrintOptions({ includeBleed: false })?.includeBleed).toBe(false);
|
|
74
|
-
expect(parsePdfPrintOptions({ includeBleed: 'false' })?.includeBleed).toBeUndefined();
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
describe('pdf.ts — parsePageFit', () => {
|
|
79
|
-
test('a known paper id passes through', () => {
|
|
80
|
-
expect(parsePageFit('a4')).toBe('a4');
|
|
81
|
-
expect(parsePageFit('letter')).toBe('letter');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('unknown id / non-string → null', () => {
|
|
85
|
-
expect(parsePageFit('not-a-paper')).toBeNull();
|
|
86
|
-
expect(parsePageFit(42)).toBeNull();
|
|
87
|
-
expect(parsePageFit(undefined)).toBeNull();
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// Dogfood follow-up (2026-07-16) — a "vector PDF" still embeds raster
|
|
92
|
-
// content (dropped photos, large-format art) as a bitmap whose density is
|
|
93
|
-
// set by the capturing context's deviceScaleFactor; `dpi` controls that.
|
|
94
|
-
describe('pdf.ts — resolvePdfDeviceScale (raster-content density, dogfood follow-up)', () => {
|
|
95
|
-
test("no dpi → 1× (today's unchanged default — NOT png.ts's 2× default)", () => {
|
|
96
|
-
expect(resolvePdfDeviceScale({})).toBe(1);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('dpi=300 → 3.125× (300/96)', () => {
|
|
100
|
-
expect(resolvePdfDeviceScale({ dpi: 300 })).toBeCloseTo(300 / 96, 10);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test('dpi=600 → 6.25×', () => {
|
|
104
|
-
expect(resolvePdfDeviceScale({ dpi: 600 })).toBeCloseTo(600 / 96, 10);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("a bare `scale` option (PNG's legacy field) is ignored — PDF has no scale concept", () => {
|
|
108
|
-
expect(resolvePdfDeviceScale({ scale: 3 })).toBe(1);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe('pdf-print-boxes — GOLDEN: A4 + 3mm bleed, no marks', () => {
|
|
113
|
-
test('MediaBox === BleedBox (full page); TrimBox insets by bleedPt exactly', async () => {
|
|
114
|
-
const resolved = resolvePrintArtboard({ paper: 'a4', bleedMm: 3 });
|
|
115
|
-
const pageWidthPt = pxToPt(resolved.widthPx);
|
|
116
|
-
const pageHeightPt = pxToPt(resolved.heightPx);
|
|
117
|
-
|
|
118
|
-
const doc = await PDFDocument.create();
|
|
119
|
-
const page = doc.addPage([pageWidthPt, pageHeightPt]);
|
|
120
|
-
applyPrintBoxesAndMarks(page, 3, NO_MARKS);
|
|
121
|
-
|
|
122
|
-
const bleedPt = mmToPt(3);
|
|
123
|
-
const media = page.getMediaBox();
|
|
124
|
-
const bleed = page.getBleedBox();
|
|
125
|
-
const trim = page.getTrimBox();
|
|
126
|
-
|
|
127
|
-
// No marks requested → no slug → MediaBox is exactly the bleed box.
|
|
128
|
-
expect(media.x).toBeCloseTo(0, 6);
|
|
129
|
-
expect(media.y).toBeCloseTo(0, 6);
|
|
130
|
-
expect(media.width).toBeCloseTo(pageWidthPt, 6);
|
|
131
|
-
expect(media.height).toBeCloseTo(pageHeightPt, 6);
|
|
132
|
-
|
|
133
|
-
expect(bleed.x).toBeCloseTo(0, 6);
|
|
134
|
-
expect(bleed.y).toBeCloseTo(0, 6);
|
|
135
|
-
expect(bleed.width).toBeCloseTo(pageWidthPt, 6);
|
|
136
|
-
expect(bleed.height).toBeCloseTo(pageHeightPt, 6);
|
|
137
|
-
|
|
138
|
-
expect(trim.x).toBeCloseTo(bleedPt, 6);
|
|
139
|
-
expect(trim.y).toBeCloseTo(bleedPt, 6);
|
|
140
|
-
expect(trim.width).toBeCloseTo(pageWidthPt - 2 * bleedPt, 6);
|
|
141
|
-
expect(trim.height).toBeCloseTo(pageHeightPt - 2 * bleedPt, 6);
|
|
142
|
-
|
|
143
|
-
// The load-bearing nesting invariant, independent of the exact numbers above.
|
|
144
|
-
expect(media.x).toBeLessThanOrEqual(bleed.x);
|
|
145
|
-
expect(media.y).toBeLessThanOrEqual(bleed.y);
|
|
146
|
-
expect(media.x + media.width).toBeGreaterThanOrEqual(bleed.x + bleed.width);
|
|
147
|
-
expect(bleed.x).toBeLessThanOrEqual(trim.x);
|
|
148
|
-
expect(bleed.y).toBeLessThanOrEqual(trim.y);
|
|
149
|
-
expect(bleed.x + bleed.width).toBeGreaterThanOrEqual(trim.x + trim.width);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
describe('pdf-print-boxes — GOLDEN: A4 + 3mm bleed + crop + registration marks', () => {
|
|
154
|
-
test('MediaBox enlarges by the exact required slug, negative origin, content-box unmoved', async () => {
|
|
155
|
-
const resolved = resolvePrintArtboard({ paper: 'a4', bleedMm: 3 });
|
|
156
|
-
const pageWidthPt = pxToPt(resolved.widthPx);
|
|
157
|
-
const pageHeightPt = pxToPt(resolved.heightPx);
|
|
158
|
-
|
|
159
|
-
const doc = await PDFDocument.create();
|
|
160
|
-
const page = doc.addPage([pageWidthPt, pageHeightPt]);
|
|
161
|
-
const printOpts: PdfPrintOptions = {
|
|
162
|
-
marks: { crop: true, registration: true, colorBars: false, pageInfo: false },
|
|
163
|
-
};
|
|
164
|
-
applyPrintBoxesAndMarks(page, 3, printOpts);
|
|
165
|
-
|
|
166
|
-
const bleedPt = mmToPt(3);
|
|
167
|
-
const slugPt = requiredSlugPt({ crop: true, registration: true });
|
|
168
|
-
const media = page.getMediaBox();
|
|
169
|
-
const bleed = page.getBleedBox();
|
|
170
|
-
const trim = page.getTrimBox();
|
|
171
|
-
|
|
172
|
-
// Exact expected pt values — the golden assertion.
|
|
173
|
-
expect(media.x).toBeCloseTo(-slugPt, 6);
|
|
174
|
-
expect(media.y).toBeCloseTo(-slugPt, 6);
|
|
175
|
-
expect(media.width).toBeCloseTo(pageWidthPt + 2 * slugPt, 6);
|
|
176
|
-
expect(media.height).toBeCloseTo(pageHeightPt + 2 * slugPt, 6);
|
|
177
|
-
// MediaBox's negative origin is the marks slug ONLY — bleed box content
|
|
178
|
-
// coordinates are untouched (still start at the page's own (0,0)).
|
|
179
|
-
expect(bleed.x).toBeCloseTo(0, 6);
|
|
180
|
-
expect(bleed.y).toBeCloseTo(0, 6);
|
|
181
|
-
expect(bleed.width).toBeCloseTo(pageWidthPt, 6);
|
|
182
|
-
expect(bleed.height).toBeCloseTo(pageHeightPt, 6);
|
|
183
|
-
expect(trim.x).toBeCloseTo(bleedPt, 6);
|
|
184
|
-
expect(trim.y).toBeCloseTo(bleedPt, 6);
|
|
185
|
-
expect(trim.width).toBeCloseTo(pageWidthPt - 2 * bleedPt, 6);
|
|
186
|
-
expect(trim.height).toBeCloseTo(pageHeightPt - 2 * bleedPt, 6);
|
|
187
|
-
|
|
188
|
-
// MediaBox ⊇ BleedBox ⊇ TrimBox, strictly (marks slug > 0 here).
|
|
189
|
-
expect(media.x).toBeLessThan(bleed.x);
|
|
190
|
-
expect(media.width + media.x).toBeGreaterThan(bleed.width + bleed.x);
|
|
191
|
-
expect(bleed.x).toBeLessThan(trim.x);
|
|
192
|
-
expect(bleed.width + bleed.x).toBeGreaterThan(trim.width + trim.x);
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
test('marks present as vector ops — saved PDF is measurably larger with marks than without', async () => {
|
|
196
|
-
const resolved = resolvePrintArtboard({ paper: 'a4', bleedMm: 3 });
|
|
197
|
-
const pageWidthPt = pxToPt(resolved.widthPx);
|
|
198
|
-
const pageHeightPt = pxToPt(resolved.heightPx);
|
|
199
|
-
|
|
200
|
-
const withoutMarks = await PDFDocument.create();
|
|
201
|
-
applyPrintBoxesAndMarks(withoutMarks.addPage([pageWidthPt, pageHeightPt]), 3, NO_MARKS);
|
|
202
|
-
const withoutBytes = await withoutMarks.save();
|
|
203
|
-
|
|
204
|
-
const withMarks = await PDFDocument.create();
|
|
205
|
-
applyPrintBoxesAndMarks(withMarks.addPage([pageWidthPt, pageHeightPt]), 3, {
|
|
206
|
-
marks: { crop: true, registration: true, colorBars: false, pageInfo: false },
|
|
207
|
-
});
|
|
208
|
-
const withBytes = await withMarks.save();
|
|
209
|
-
|
|
210
|
-
// 8 crop-mark line segments + 4 registration circles (each an approximated
|
|
211
|
-
// bezier ellipse) + 8 crosshair segments is a non-trivial amount of vector
|
|
212
|
-
// content — the byte-size delta is a reliable proxy without depending on
|
|
213
|
-
// pdf-lib's internal (and version-specific) content-stream compression
|
|
214
|
-
// format for a literal operator-string grep.
|
|
215
|
-
expect(withBytes.byteLength).toBeGreaterThan(withoutBytes.byteLength);
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
describe('pdf-print-boxes — includeBleed:false crops the bleed strip out', () => {
|
|
220
|
-
test('MediaBox/BleedBox collapse onto TrimBox (degenerate but valid nesting)', async () => {
|
|
221
|
-
const resolved = resolvePrintArtboard({ paper: 'a4', bleedMm: 3 });
|
|
222
|
-
const pageWidthPt = pxToPt(resolved.widthPx);
|
|
223
|
-
const pageHeightPt = pxToPt(resolved.heightPx);
|
|
224
|
-
const bleedPt = mmToPt(3);
|
|
225
|
-
|
|
226
|
-
const doc = await PDFDocument.create();
|
|
227
|
-
const page = doc.addPage([pageWidthPt, pageHeightPt]);
|
|
228
|
-
applyPrintBoxesAndMarks(page, 3, { ...NO_MARKS, includeBleed: false });
|
|
229
|
-
|
|
230
|
-
const media = page.getMediaBox();
|
|
231
|
-
const bleed = page.getBleedBox();
|
|
232
|
-
const trim = page.getTrimBox();
|
|
233
|
-
|
|
234
|
-
expect(media.x).toBeCloseTo(bleedPt, 6);
|
|
235
|
-
expect(media.y).toBeCloseTo(bleedPt, 6);
|
|
236
|
-
expect(media.width).toBeCloseTo(pageWidthPt - 2 * bleedPt, 6);
|
|
237
|
-
expect(bleed.x).toBeCloseTo(trim.x, 6);
|
|
238
|
-
expect(bleed.width).toBeCloseTo(trim.width, 6);
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
// RCA issue-pdf-print-export-marks-missing — the shape that shipped broken:
|
|
243
|
-
// two `kind="print"` artboards sharing one `print` spec via a top-level const
|
|
244
|
-
// (AlligatorsAcko.tsx's actual authoring pattern), not a repeated inline
|
|
245
|
-
// literal per artboard. The multi-page loop in exporters/pdf.ts `run()`
|
|
246
|
-
// re-resolves readArtboardPrintProp independently per written page (keyed by
|
|
247
|
-
// the artboard's own data-dc-screen id — see safeArtboardFilename), so
|
|
248
|
-
// asserting it resolves per-artboard-id here is the load-bearing regression
|
|
249
|
-
// guard: both pages must get the SAME resolved geometry, not just page 1.
|
|
250
|
-
describe('readArtboardPrintProp — shared const print spec resolves for every page (multi-page correlation)', () => {
|
|
251
|
-
const canvas = [
|
|
252
|
-
'const A1_PRINT = { paper: "a1", orientation: "portrait", bleedMm: 3 } as const;',
|
|
253
|
-
'export default function Demo() {',
|
|
254
|
-
' return (',
|
|
255
|
-
' <DesignCanvas>',
|
|
256
|
-
' <DCArtboard id="acko-front" label="A" kind="print" width={816} height={1146} print={A1_PRINT as any}>',
|
|
257
|
-
' <div>front</div>',
|
|
258
|
-
' </DCArtboard>',
|
|
259
|
-
' <DCArtboard id="acko-back" label="B" kind="print" width={816} height={1146} print={A1_PRINT as any}>',
|
|
260
|
-
' <div>back</div>',
|
|
261
|
-
' </DCArtboard>',
|
|
262
|
-
' </DesignCanvas>',
|
|
263
|
-
' );',
|
|
264
|
-
'}',
|
|
265
|
-
].join('\n');
|
|
266
|
-
|
|
267
|
-
test('both artboard ids resolve to the same non-null print geometry, driving non-degenerate boxes+marks on each page', async () => {
|
|
268
|
-
const front = readArtboardPrintProp('/abs/AlligatorsAcko.tsx', canvas, 'acko-front');
|
|
269
|
-
const back = readArtboardPrintProp('/abs/AlligatorsAcko.tsx', canvas, 'acko-back');
|
|
270
|
-
expect(front).toEqual({ paper: 'a1', orientation: 'portrait', bleedMm: 3 });
|
|
271
|
-
expect(back).toEqual(front);
|
|
272
|
-
|
|
273
|
-
const printOpts: PdfPrintOptions = {
|
|
274
|
-
marks: { crop: true, registration: true, colorBars: false, pageInfo: false },
|
|
275
|
-
};
|
|
276
|
-
for (const printProp of [front, back]) {
|
|
277
|
-
if (!printProp) throw new Error('expected a resolved print prop');
|
|
278
|
-
const bleedMm = typeof printProp.bleedMm === 'number' ? printProp.bleedMm : 0;
|
|
279
|
-
const resolved = resolvePrintArtboard({ paper: printProp.paper as string, bleedMm });
|
|
280
|
-
const pageWidthPt = pxToPt(resolved.widthPx);
|
|
281
|
-
const pageHeightPt = pxToPt(resolved.heightPx);
|
|
282
|
-
const doc = await PDFDocument.create();
|
|
283
|
-
const page = doc.addPage([pageWidthPt, pageHeightPt]);
|
|
284
|
-
applyPrintBoxesAndMarks(page, bleedMm, printOpts);
|
|
285
|
-
const media = page.getMediaBox();
|
|
286
|
-
const trim = page.getTrimBox();
|
|
287
|
-
// Non-degenerate: TrimBox strictly inside MediaBox on every side — the
|
|
288
|
-
// exact thing that silently failed to happen when printProp was null.
|
|
289
|
-
expect(media.width).toBeGreaterThan(trim.width);
|
|
290
|
-
expect(media.height).toBeGreaterThan(trim.height);
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
describe('pdf-print-boxes — applyPageFit (non-print artboard scale-to-paper)', () => {
|
|
296
|
-
test('the resulting page is sized to the target paper, content preserved', async () => {
|
|
297
|
-
const doc = await PDFDocument.create();
|
|
298
|
-
// A wide, non-print-sized artboard (e.g. a 1440×1024 digital screen at 96dpi).
|
|
299
|
-
const origWidthPt = pxToPt(1440);
|
|
300
|
-
const origHeightPt = pxToPt(1024);
|
|
301
|
-
doc.addPage([origWidthPt, origHeightPt]);
|
|
302
|
-
expect(doc.getPageCount()).toBe(1);
|
|
303
|
-
|
|
304
|
-
await applyPageFit(doc, 0, 'a4');
|
|
305
|
-
|
|
306
|
-
expect(doc.getPageCount()).toBe(1); // swapped in place, not appended
|
|
307
|
-
const page = doc.getPage(0);
|
|
308
|
-
// Expected size mirrors applyPageFit's OWN derivation (mmToPt of the
|
|
309
|
-
// preset's raw mm dims via trimSizeMm) — NOT a px-rounded roundtrip via
|
|
310
|
-
// resolvePrintArtboard, which would introduce a spurious sub-pt mismatch.
|
|
311
|
-
const preset = getPaperPreset('a4');
|
|
312
|
-
if (!preset) throw new Error('a4 preset missing');
|
|
313
|
-
const { widthMm, heightMm } = trimSizeMm(preset, 'portrait');
|
|
314
|
-
const { width, height } = page.getSize();
|
|
315
|
-
expect(width).toBeCloseTo(mmToPt(widthMm), 6);
|
|
316
|
-
expect(height).toBeCloseTo(mmToPt(heightMm), 6);
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
test('unknown paper id is a no-op (never throws, never mutates)', async () => {
|
|
320
|
-
const doc = await PDFDocument.create();
|
|
321
|
-
doc.addPage([100, 100]);
|
|
322
|
-
await applyPageFit(doc, 0, 'not-a-paper');
|
|
323
|
-
expect(doc.getPageCount()).toBe(1);
|
|
324
|
-
expect(doc.getPage(0).getSize()).toEqual({ width: 100, height: 100 });
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
// DDR-232 follow-up — the WORKER-lane print gap. pdf.ts read the artboard's
|
|
329
|
-
// `print` prop from the canvas source ON DISK; the render worker holds no
|
|
330
|
-
// checkout (DDR-230 §1), so every cloud print PDF silently came back with no
|
|
331
|
-
// BleedBox/TrimBox and no marks — the fall-through was "not a print artboard".
|
|
332
|
-
// The cell now resolves every print artboard's prop up front
|
|
333
|
-
// (readAllArtboardPrintProps) and ships it in the job as `options.printProps`.
|
|
334
|
-
describe('readAllArtboardPrintProps — what the cell ships to the worker', () => {
|
|
335
|
-
const SRC = `import { DesignCanvas, DCSection, DCArtboard } from "@maude/canvas-lib";
|
|
336
|
-
export default function C() {
|
|
337
|
-
return (
|
|
338
|
-
<DesignCanvas>
|
|
339
|
-
<DCSection id="s" title="t">
|
|
340
|
-
<DCArtboard id="screen" width={400} height={300}><div/></DCArtboard>
|
|
341
|
-
<DCArtboard id="flyer" kind="print" print={{ paper: "a6", bleedMm: 3 }} width={397} height={559}><div/></DCArtboard>
|
|
342
|
-
<DCArtboard id="poster" kind="print" print={{ paper: "a3" }} width={1123} height={1587}><div/></DCArtboard>
|
|
343
|
-
</DCSection>
|
|
344
|
-
</DesignCanvas>
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
`;
|
|
348
|
-
test('collects every print artboard by id and skips screen artboards', async () => {
|
|
349
|
-
const { readAllArtboardPrintProps } = await import('../canvas-edit.ts');
|
|
350
|
-
const props = readAllArtboardPrintProps('/x/c.tsx', SRC);
|
|
351
|
-
expect(Object.keys(props).sort()).toEqual(['flyer', 'poster']);
|
|
352
|
-
expect(props.flyer).toEqual({ paper: 'a6', bleedMm: 3 });
|
|
353
|
-
expect(props.poster).toEqual({ paper: 'a3' });
|
|
354
|
-
});
|
|
355
|
-
test('an unparsable canvas contributes nothing rather than throwing', async () => {
|
|
356
|
-
const { readAllArtboardPrintProps } = await import('../canvas-edit.ts');
|
|
357
|
-
expect(readAllArtboardPrintProps('/x/c.tsx', 'export default <<<')).toEqual({});
|
|
358
|
-
});
|
|
359
|
-
});
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
// peer-selection-follows-camera — the collaboration counterpart of the
|
|
2
|
-
// annotation-resize-handle fix (9ba40b96). A peer's selection frame is measured
|
|
3
|
-
// off the LOCAL DOM every render, so it only tracks a pan/zoom if the component
|
|
4
|
-
// actually re-renders while the camera moves. Both halos are `memo`, and through
|
|
5
|
-
// a gesture the peer's awareness is unchanged — so without a viewport prop that
|
|
6
|
-
// ticks with the camera, memo bails out and the frame sits frozen in screen
|
|
7
|
-
// space while the artboard it outlines slides away underneath it.
|
|
8
|
-
//
|
|
9
|
-
// Needs a live DOM (memo bailout + a stubbed getBoundingClientRect) — register
|
|
10
|
-
// happy-dom + drive a createRoot, same convention as mode-toggle.test.tsx.
|
|
11
|
-
|
|
12
|
-
import { afterAll, afterEach, beforeAll, describe, expect, test } from 'bun:test';
|
|
13
|
-
import { GlobalRegistrator } from '@happy-dom/global-registrator';
|
|
14
|
-
|
|
15
|
-
beforeAll(() => {
|
|
16
|
-
GlobalRegistrator.register();
|
|
17
|
-
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
18
|
-
});
|
|
19
|
-
afterAll(() => {
|
|
20
|
-
GlobalRegistrator.unregister();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
import { act, type ReactNode } from 'react';
|
|
24
|
-
import { createRoot, type Root } from 'react-dom/client';
|
|
25
|
-
import { PeerAnnotationSelection, PeerSelection } from '../cursors-overlay.tsx';
|
|
26
|
-
import type { ForeignAwareness } from '../use-collab.tsx';
|
|
27
|
-
|
|
28
|
-
// The camera the stubbed measurements project through — stands in for the world
|
|
29
|
-
// plane's CSS transform, which is what really moves the target under a pan.
|
|
30
|
-
let camera = { x: 0, y: 0, zoom: 1 };
|
|
31
|
-
|
|
32
|
-
/** A target element whose screen rect follows `camera`, like real canvas content. */
|
|
33
|
-
function target(
|
|
34
|
-
attr: string,
|
|
35
|
-
value: string,
|
|
36
|
-
world: { x: number; y: number; w: number; h: number }
|
|
37
|
-
) {
|
|
38
|
-
const el = document.createElement('div');
|
|
39
|
-
el.setAttribute(attr, value);
|
|
40
|
-
el.getBoundingClientRect = () =>
|
|
41
|
-
({
|
|
42
|
-
left: world.x * camera.zoom + camera.x,
|
|
43
|
-
top: world.y * camera.zoom + camera.y,
|
|
44
|
-
width: world.w * camera.zoom,
|
|
45
|
-
height: world.h * camera.zoom,
|
|
46
|
-
}) as DOMRect;
|
|
47
|
-
document.body.appendChild(el);
|
|
48
|
-
return el;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
let root: Root | null = null;
|
|
52
|
-
let host: HTMLElement | null = null;
|
|
53
|
-
|
|
54
|
-
function mount(ui: ReactNode): void {
|
|
55
|
-
host = document.createElement('div');
|
|
56
|
-
document.body.appendChild(host);
|
|
57
|
-
root = createRoot(host);
|
|
58
|
-
act(() => {
|
|
59
|
-
root?.render(ui);
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function rerender(ui: ReactNode): void {
|
|
64
|
-
act(() => {
|
|
65
|
-
root?.render(ui);
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function frameTransform(): string {
|
|
70
|
-
const el = host?.querySelector('.dc-peer-selection') as HTMLElement | null;
|
|
71
|
-
return el?.style.transform ?? '';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
afterEach(() => {
|
|
75
|
-
act(() => {
|
|
76
|
-
root?.unmount();
|
|
77
|
-
});
|
|
78
|
-
host?.remove();
|
|
79
|
-
root = null;
|
|
80
|
-
host = null;
|
|
81
|
-
document.body.innerHTML = '';
|
|
82
|
-
camera = { x: 0, y: 0, zoom: 1 };
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// One stable object across the whole test — a real pan/zoom does not touch the
|
|
86
|
-
// peer's awareness, and that identity is exactly what makes memo bail out.
|
|
87
|
-
const peer = {
|
|
88
|
-
clientID: 7,
|
|
89
|
-
name: 'you@local.test',
|
|
90
|
-
color: 'oklch(56% 0.17 50)',
|
|
91
|
-
cursor: null,
|
|
92
|
-
selection: { cssPath: '[data-cd-id="hero"]' },
|
|
93
|
-
annotationSelection: ['stroke-1'],
|
|
94
|
-
} as unknown as ForeignAwareness;
|
|
95
|
-
|
|
96
|
-
describe('cursors-overlay / PeerSelection', () => {
|
|
97
|
-
test('the frame follows a pan', () => {
|
|
98
|
-
target('data-cd-id', 'hero', { x: 100, y: 60, w: 200, h: 120 });
|
|
99
|
-
mount(<PeerSelection peer={peer} viewport={{ x: 0, y: 0, zoom: 1 }} />);
|
|
100
|
-
expect(frameTransform()).toBe('translate(100px, 60px)');
|
|
101
|
-
|
|
102
|
-
camera = { x: -40, y: -25, zoom: 1 };
|
|
103
|
-
rerender(<PeerSelection peer={peer} viewport={camera} />);
|
|
104
|
-
expect(frameTransform()).toBe('translate(60px, 35px)');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('the frame follows a zoom (position AND size)', () => {
|
|
108
|
-
target('data-cd-id', 'hero', { x: 100, y: 60, w: 200, h: 120 });
|
|
109
|
-
mount(<PeerSelection peer={peer} viewport={{ x: 0, y: 0, zoom: 1 }} />);
|
|
110
|
-
|
|
111
|
-
camera = { x: 0, y: 0, zoom: 2 };
|
|
112
|
-
rerender(<PeerSelection peer={peer} viewport={camera} />);
|
|
113
|
-
const el = host?.querySelector('.dc-peer-selection') as HTMLElement;
|
|
114
|
-
expect(el.style.transform).toBe('translate(200px, 120px)');
|
|
115
|
-
expect(el.style.width).toBe('400px');
|
|
116
|
-
expect(el.style.height).toBe('240px');
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe('cursors-overlay / PeerAnnotationSelection', () => {
|
|
121
|
-
test('the stroke halo follows a pan', () => {
|
|
122
|
-
target('data-id', 'stroke-1', { x: 100, y: 60, w: 200, h: 120 });
|
|
123
|
-
mount(<PeerAnnotationSelection peer={peer} viewport={{ x: 0, y: 0, zoom: 1 }} />);
|
|
124
|
-
// Padded 3px so the halo sits OUTSIDE the stroke.
|
|
125
|
-
expect(frameTransform()).toBe('translate(97px, 57px)');
|
|
126
|
-
|
|
127
|
-
camera = { x: -40, y: -25, zoom: 1 };
|
|
128
|
-
rerender(<PeerAnnotationSelection peer={peer} viewport={camera} />);
|
|
129
|
-
expect(frameTransform()).toBe('translate(57px, 32px)');
|
|
130
|
-
});
|
|
131
|
-
});
|