@1agh/maude 1.0.11 → 1.3.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 -1
- package/apps/studio/acp/bridge.ts +18 -13
- package/apps/studio/acp/index.ts +43 -7
- package/apps/studio/acp/transcript-io.ts +394 -0
- package/apps/studio/acp/transcript.ts +103 -38
- package/apps/studio/annotations-layer.tsx +191 -116
- package/apps/studio/annotations-model.ts +39 -0
- package/apps/studio/annotations-sync.ts +50 -0
- package/apps/studio/api.ts +821 -55
- package/apps/studio/bin/_import-figma.mjs +57 -11
- package/apps/studio/bin/_seed-fixture.mjs +163 -0
- package/apps/studio/bin/annotate.mjs +3 -1
- package/apps/studio/bin/server-up.sh +17 -2
- package/apps/studio/canvas-edit.ts +391 -7
- package/apps/studio/canvas-imports.ts +89 -0
- package/apps/studio/canvas-lib.tsx +82 -18
- package/apps/studio/canvas-notice-message.ts +16 -0
- package/apps/studio/canvas-notifications.tsx +19 -0
- package/apps/studio/canvas-shell.tsx +57 -6
- package/apps/studio/client/app.jsx +943 -248
- package/apps/studio/client/apply-edit-request.ts +55 -0
- package/apps/studio/client/export-center.jsx +125 -67
- package/apps/studio/client/github.js +38 -1
- package/apps/studio/client/index-loader.ts +76 -0
- package/apps/studio/client/panels/CloudBar.jsx +52 -6
- package/apps/studio/client/panels/GitPanel.jsx +103 -0
- package/apps/studio/client/panels/OnboardingWizard.jsx +36 -7
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +92 -27
- package/apps/studio/client/panels/SourceConflictPanel.jsx +166 -0
- package/apps/studio/client/panels/SyncPanel.jsx +238 -4
- package/apps/studio/client/panels/TeamProjects.jsx +376 -0
- package/apps/studio/client/panels/file-deep-link-dialog.jsx +54 -0
- package/apps/studio/client/photo-knobs.jsx +3 -0
- package/apps/studio/client/report-bug.jsx +19 -1
- package/apps/studio/client/share-dialog.jsx +91 -0
- package/apps/studio/client/share-link.js +94 -0
- package/apps/studio/client/styles/3-shell-maude.css +69 -18
- package/apps/studio/client/styles/4-components.css +44 -1
- package/apps/studio/client/tree-row-menu.jsx +48 -3
- package/apps/studio/client/whats-new.jsx +26 -32
- package/apps/studio/cloud/endpoints.ts +132 -0
- package/apps/studio/collab/index.ts +4 -0
- package/apps/studio/collab/persistence.ts +137 -17
- package/apps/studio/collab/registry.ts +19 -21
- package/apps/studio/collab/room.ts +13 -2
- package/apps/studio/context.ts +59 -0
- package/apps/studio/debug-bundle.ts +13 -0
- package/apps/studio/dist/client.bundle.js +1911 -1879
- package/apps/studio/dist/runtime/.min-sizes.json +1 -0
- package/apps/studio/dist/runtime/sonner.js +1 -0
- 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 +296 -19
- package/apps/studio/inspect.ts +19 -0
- package/apps/studio/managed-projects.ts +150 -0
- package/apps/studio/mem.ts +45 -9
- package/apps/studio/notifications.tsx +292 -0
- package/apps/studio/runtime-bundle.ts +2 -0
- package/apps/studio/server.ts +69 -6
- package/apps/studio/sync/accepted-cold-start.ts +225 -0
- package/apps/studio/sync/accepted-link.ts +320 -0
- package/apps/studio/sync/action-stage.ts +343 -0
- package/apps/studio/sync/agent.ts +104 -45
- package/apps/studio/sync/asset-push.ts +1 -47
- package/apps/studio/sync/cell-file-events.ts +3 -0
- package/apps/studio/sync/codec.ts +192 -34
- 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/ctl-provider.ts +15 -2
- package/apps/studio/sync/document-discovery.ts +81 -0
- package/apps/studio/sync/file-ledger.ts +117 -0
- package/apps/studio/sync/file-membership.ts +10 -2
- package/apps/studio/sync/file-plane.ts +926 -47
- package/apps/studio/sync/index.ts +1786 -36
- package/apps/studio/sync/journal-client.ts +13 -1
- package/apps/studio/sync/migrate-seed.ts +150 -22
- package/apps/studio/sync/poke.ts +4 -2
- package/apps/studio/sync/presentation.ts +311 -5
- package/apps/studio/sync/projection.ts +820 -23
- package/apps/studio/sync/repeated-module.ts +73 -0
- package/apps/studio/sync/retry-after.ts +169 -0
- package/apps/studio/sync/revision-barrier.ts +129 -0
- package/apps/studio/sync/seed-progress.ts +218 -0
- package/apps/studio/sync/seed-repair.ts +46 -0
- package/apps/studio/sync/source-merge.ts +100 -0
- package/apps/studio/sync/source-ops.ts +289 -0
- package/apps/studio/sync/source-recovery.ts +70 -0
- package/apps/studio/sync/source-validation.ts +56 -0
- package/apps/studio/sync/status.ts +201 -6
- package/apps/studio/sync/transaction-client.ts +460 -0
- package/apps/studio/sync/transport-error.ts +111 -0
- package/apps/studio/sync/writer-registry.ts +236 -0
- package/apps/studio/text-caret.ts +35 -0
- package/apps/studio/undo-hud.tsx +9 -87
- package/apps/studio/use-canvas-media-drop.tsx +4 -39
- package/apps/studio/whats-new.json +173 -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 +892 -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 +33 -9
- package/plugins/design/dependencies.json +3 -3
- package/plugins/design/templates/_shell.html +47 -0
- package/plugins/flow/.claude-plugin/config.schema.json +8 -0
- package/plugins/flow/dependencies.json +3 -3
- 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,92 +0,0 @@
|
|
|
1
|
-
// Screenshot-browser resolver (bundled-screenshots feature). Covers the pure
|
|
2
|
-
// resolution priority; the actual download (priority 5) is exercised only when no
|
|
3
|
-
// browser exists on the host — untestable in CI where system Chrome / a Playwright
|
|
4
|
-
// headless-shell are usually present, so we assert `download:false` never fetches.
|
|
5
|
-
|
|
6
|
-
import { describe, expect, test } from 'bun:test';
|
|
7
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
8
|
-
import { tmpdir } from 'node:os';
|
|
9
|
-
import { join } from 'node:path';
|
|
10
|
-
|
|
11
|
-
import { CFT_VERSION_RE, isTrustedDownloadUrl, resolveBrowser } from '../bin/_ensure-browser.mjs';
|
|
12
|
-
|
|
13
|
-
function withEnv<T>(patch: Record<string, string | undefined>, fn: () => T): T {
|
|
14
|
-
const saved: Record<string, string | undefined> = {};
|
|
15
|
-
for (const k of Object.keys(patch)) {
|
|
16
|
-
saved[k] = process.env[k];
|
|
17
|
-
if (patch[k] === undefined) delete process.env[k];
|
|
18
|
-
else process.env[k] = patch[k];
|
|
19
|
-
}
|
|
20
|
-
try {
|
|
21
|
-
return fn();
|
|
22
|
-
} finally {
|
|
23
|
-
for (const k of Object.keys(patch)) {
|
|
24
|
-
if (saved[k] === undefined) delete process.env[k];
|
|
25
|
-
else process.env[k] = saved[k];
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe('resolveBrowser — resolution priority', () => {
|
|
31
|
-
test('AGENT_BROWSER_EXECUTABLE_PATH override wins', async () => {
|
|
32
|
-
const r = await withEnv({ AGENT_BROWSER_EXECUTABLE_PATH: import.meta.path }, () =>
|
|
33
|
-
resolveBrowser({ download: false })
|
|
34
|
-
);
|
|
35
|
-
expect(r.source).toBe('override');
|
|
36
|
-
expect(r.path).toBe(import.meta.path);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('a cached chrome-headless-shell in MAUDE_BROWSERS_DIR resolves before system Chrome', async () => {
|
|
40
|
-
const dir = mkdtempSync(join(tmpdir(), 'maude-browsers-'));
|
|
41
|
-
const sub = join(dir, 'chrome-headless-shell-1.2.3-mac-arm64');
|
|
42
|
-
mkdirSync(sub, { recursive: true });
|
|
43
|
-
const exeName =
|
|
44
|
-
process.platform === 'win32' ? 'chrome-headless-shell.exe' : 'chrome-headless-shell';
|
|
45
|
-
const exe = join(sub, exeName);
|
|
46
|
-
writeFileSync(exe, '#!/bin/sh\n');
|
|
47
|
-
try {
|
|
48
|
-
const r = await withEnv(
|
|
49
|
-
{
|
|
50
|
-
MAUDE_BROWSERS_DIR: dir,
|
|
51
|
-
AGENT_BROWSER_EXECUTABLE_PATH: undefined,
|
|
52
|
-
MAUDE_BROWSER_EXECUTABLE: undefined,
|
|
53
|
-
},
|
|
54
|
-
() => resolveBrowser({ download: false })
|
|
55
|
-
);
|
|
56
|
-
expect(r.source).toBe('cache');
|
|
57
|
-
expect(r.path).toBe(exe);
|
|
58
|
-
} finally {
|
|
59
|
-
rmSync(dir, { recursive: true, force: true });
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('download:false never fetches (source is never "downloaded")', async () => {
|
|
64
|
-
const r = await resolveBrowser({ download: false });
|
|
65
|
-
expect(r.source).not.toBe('downloaded');
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe('download hardening (DDR-144 security fixes)', () => {
|
|
70
|
-
test('isTrustedDownloadUrl accepts only https on the allowlisted CDN hosts (F2)', () => {
|
|
71
|
-
expect(
|
|
72
|
-
isTrustedDownloadUrl('https://storage.googleapis.com/chrome-for-testing-public/x.zip')
|
|
73
|
-
).toBe(true);
|
|
74
|
-
expect(isTrustedDownloadUrl('https://googlechromelabs.github.io/x.json')).toBe(true);
|
|
75
|
-
// plaintext scheme (MITM-able) rejected
|
|
76
|
-
expect(isTrustedDownloadUrl('http://storage.googleapis.com/x.zip')).toBe(false);
|
|
77
|
-
// foreign host (manifest tampering / redirect) rejected
|
|
78
|
-
expect(isTrustedDownloadUrl('https://evil.example.com/x.zip')).toBe(false);
|
|
79
|
-
// garbage rejected, no throw
|
|
80
|
-
expect(isTrustedDownloadUrl('not a url')).toBe(false);
|
|
81
|
-
expect(isTrustedDownloadUrl('')).toBe(false);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('CFT_VERSION_RE accepts real versions, rejects shell-injection payloads (F3)', () => {
|
|
85
|
-
expect(CFT_VERSION_RE.test('150.0.7871.46')).toBe(true);
|
|
86
|
-
expect(CFT_VERSION_RE.test('120')).toBe(true);
|
|
87
|
-
// the PowerShell -Command break-out attempt
|
|
88
|
-
expect(CFT_VERSION_RE.test("1.0'; Remove-Item C:\\ -Recurse #")).toBe(false);
|
|
89
|
-
expect(CFT_VERSION_RE.test('$(rm -rf /)')).toBe(false);
|
|
90
|
-
expect(CFT_VERSION_RE.test('../../etc')).toBe(false);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
// equal-spacing-detector — T27 (Wave 3). Pure detector fixtures.
|
|
2
|
-
|
|
3
|
-
import { describe, expect, test } from 'bun:test';
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
computeAlign,
|
|
7
|
-
computeDistribute,
|
|
8
|
-
computePairGap,
|
|
9
|
-
computeTidyGrid,
|
|
10
|
-
detectEqualSpacing,
|
|
11
|
-
type KeyedRect,
|
|
12
|
-
} from '../equal-spacing-detector.ts';
|
|
13
|
-
import type { Rect } from '../use-snap-guides.tsx';
|
|
14
|
-
|
|
15
|
-
const r = (x: number, y: number, w = 100, h = 60): Rect => ({ x, y, w, h });
|
|
16
|
-
const kr = (key: string, x: number, y: number, w = 100, h = 60): KeyedRect => ({
|
|
17
|
-
key,
|
|
18
|
-
x,
|
|
19
|
-
y,
|
|
20
|
-
w,
|
|
21
|
-
h,
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe('detectEqualSpacing / preconditions', () => {
|
|
25
|
-
test('fewer than 3 rects → null', () => {
|
|
26
|
-
expect(detectEqualSpacing([], 'x')).toBeNull();
|
|
27
|
-
expect(detectEqualSpacing([r(0, 0)], 'x')).toBeNull();
|
|
28
|
-
expect(detectEqualSpacing([r(0, 0), r(200, 0)], 'x')).toBeNull();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('overlapping rects → null (gap < 0)', () => {
|
|
32
|
-
// r1 spans 0..100, r2 starts at 90 (overlap by 10)
|
|
33
|
-
expect(detectEqualSpacing([r(0, 0), r(90, 0), r(220, 0)], 'x')).toBeNull();
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('detectEqualSpacing / horizontal axis', () => {
|
|
38
|
-
test('3 rects, equal gap 20 → detects with gap=20 + 2 midpoints', () => {
|
|
39
|
-
// 0..100, 120..220, 240..340 — gap = 20
|
|
40
|
-
const out = detectEqualSpacing([r(0, 0), r(120, 0), r(240, 0)], 'x');
|
|
41
|
-
expect(out).not.toBeNull();
|
|
42
|
-
expect(out?.axis).toBe('x');
|
|
43
|
-
expect(out?.gapPx).toBe(20);
|
|
44
|
-
expect(out?.midpoints).toHaveLength(2);
|
|
45
|
-
expect(out?.midpoints[0]).toEqual({ x: 110, y: 30 });
|
|
46
|
-
expect(out?.midpoints[1]).toEqual({ x: 230, y: 30 });
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('uneven gaps outside tolerance → null', () => {
|
|
50
|
-
// gaps: 20, 30
|
|
51
|
-
const out = detectEqualSpacing([r(0, 0), r(120, 0), r(250, 0)], 'x');
|
|
52
|
-
expect(out).toBeNull();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test('within 1 px tolerance is accepted', () => {
|
|
56
|
-
// gaps: 20, 21 — within default tolerance of 1
|
|
57
|
-
const out = detectEqualSpacing([r(0, 0), r(120, 0), r(241, 0)], 'x');
|
|
58
|
-
expect(out).not.toBeNull();
|
|
59
|
-
expect(out?.gapPx).toBeGreaterThanOrEqual(20);
|
|
60
|
-
expect(out?.gapPx).toBeLessThanOrEqual(21);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('explicit tolerance allows wider band', () => {
|
|
64
|
-
// gaps: 20, 25
|
|
65
|
-
const tightFail = detectEqualSpacing([r(0, 0), r(120, 0), r(245, 0)], 'x');
|
|
66
|
-
expect(tightFail).toBeNull();
|
|
67
|
-
const loosePass = detectEqualSpacing([r(0, 0), r(120, 0), r(245, 0)], 'x', {
|
|
68
|
-
tolerancePx: 5,
|
|
69
|
-
});
|
|
70
|
-
expect(loosePass).not.toBeNull();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test('rects passed in random order → sorted internally', () => {
|
|
74
|
-
const out = detectEqualSpacing([r(240, 0), r(0, 0), r(120, 0)], 'x');
|
|
75
|
-
expect(out).not.toBeNull();
|
|
76
|
-
expect(out?.gapPx).toBe(20);
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
describe('detectEqualSpacing / vertical axis', () => {
|
|
81
|
-
test('3 rects stacked, equal gap 30 → detects', () => {
|
|
82
|
-
// y: 0..60, 90..150, 180..240 — gap 30
|
|
83
|
-
const out = detectEqualSpacing([r(0, 0), r(0, 90), r(0, 180)], 'y');
|
|
84
|
-
expect(out).not.toBeNull();
|
|
85
|
-
expect(out?.axis).toBe('y');
|
|
86
|
-
expect(out?.gapPx).toBe(30);
|
|
87
|
-
expect(out?.midpoints).toHaveLength(2);
|
|
88
|
-
expect(out?.midpoints[0]?.y).toBe(75); // 60 + 30/2
|
|
89
|
-
expect(out?.midpoints[1]?.y).toBe(165);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe('detectEqualSpacing / 4+ rects', () => {
|
|
94
|
-
test('4 rects equally distributed → 3 midpoints', () => {
|
|
95
|
-
// gaps 20, 20, 20
|
|
96
|
-
const out = detectEqualSpacing([r(0, 0), r(120, 0), r(240, 0), r(360, 0)], 'x');
|
|
97
|
-
expect(out).not.toBeNull();
|
|
98
|
-
expect(out?.midpoints).toHaveLength(3);
|
|
99
|
-
expect(out?.gapPx).toBe(20);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('one bad gap in a 4-rect set → null', () => {
|
|
103
|
-
// gaps 20, 50, 20
|
|
104
|
-
const out = detectEqualSpacing([r(0, 0), r(120, 0), r(270, 0), r(390, 0)], 'x');
|
|
105
|
-
expect(out).toBeNull();
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// Task L7 — Alt-hover measurement pairwise gap.
|
|
110
|
-
|
|
111
|
-
describe('computePairGap', () => {
|
|
112
|
-
test('measures the x-axis gap regardless of argument order', () => {
|
|
113
|
-
const a = r(0, 0, 100, 50);
|
|
114
|
-
const b = r(150, 20, 80, 50);
|
|
115
|
-
const fwd = computePairGap(a, b, 'x');
|
|
116
|
-
const rev = computePairGap(b, a, 'x');
|
|
117
|
-
expect(fwd).toEqual({ axis: 'x', gap: 50, from: 100, cross: 35 });
|
|
118
|
-
expect(rev).toEqual(fwd);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('measures the y-axis gap', () => {
|
|
122
|
-
const a = r(0, 0, 50, 60);
|
|
123
|
-
const b = r(10, 150, 50, 60);
|
|
124
|
-
const out = computePairGap(a, b, 'y');
|
|
125
|
-
expect(out).toEqual({ axis: 'y', gap: 90, from: 60, cross: 30 });
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test('overlapping on the axis → null (no gap to show)', () => {
|
|
129
|
-
const a = r(0, 0, 100, 50);
|
|
130
|
-
const b = r(50, 0, 100, 50); // overlaps a on x
|
|
131
|
-
expect(computePairGap(a, b, 'x')).toBeNull();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test('touching (gap === 0) → null', () => {
|
|
135
|
-
const a = r(0, 0, 100, 50);
|
|
136
|
-
const b = r(100, 0, 100, 50);
|
|
137
|
-
expect(computePairGap(a, b, 'x')).toBeNull();
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
// Task L5 — distribute / align / tidy-up pure math.
|
|
142
|
-
|
|
143
|
-
describe('computeAlign', () => {
|
|
144
|
-
test('fewer than 2 rects → []', () => {
|
|
145
|
-
expect(computeAlign([], 'left')).toEqual([]);
|
|
146
|
-
expect(computeAlign([kr('a', 0, 0)], 'left')).toEqual([]);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test('left/right/top/bottom hold the union-bbox edge', () => {
|
|
150
|
-
const rects = [kr('a', 0, 0, 100, 50), kr('b', 40, 100, 60, 50), kr('c', 200, 30, 100, 50)];
|
|
151
|
-
const left = computeAlign(rects, 'left');
|
|
152
|
-
// union xMin = 0 → 'a' already at x=0 is a no-op (skipped); b/c move.
|
|
153
|
-
expect(left).toEqual(
|
|
154
|
-
expect.arrayContaining([
|
|
155
|
-
{ key: 'b', x: 0, y: 100 },
|
|
156
|
-
{ key: 'c', x: 0, y: 30 },
|
|
157
|
-
])
|
|
158
|
-
);
|
|
159
|
-
expect(left.find((m) => m.key === 'a')).toBeUndefined();
|
|
160
|
-
|
|
161
|
-
const right = computeAlign(rects, 'right');
|
|
162
|
-
// union xMax = 300 (c: 200+100)
|
|
163
|
-
expect(right.find((m) => m.key === 'a')).toEqual({ key: 'a', x: 200, y: 0 });
|
|
164
|
-
expect(right.find((m) => m.key === 'b')).toEqual({ key: 'b', x: 240, y: 100 });
|
|
165
|
-
expect(right.find((m) => m.key === 'c')).toBeUndefined(); // already at right edge
|
|
166
|
-
|
|
167
|
-
const top = computeAlign(rects, 'top');
|
|
168
|
-
expect(top.find((m) => m.key === 'a')).toBeUndefined(); // already yMin=0
|
|
169
|
-
expect(top.find((m) => m.key === 'b')).toEqual({ key: 'b', x: 40, y: 0 });
|
|
170
|
-
expect(top.find((m) => m.key === 'c')).toEqual({ key: 'c', x: 200, y: 0 });
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test('center-x / center-y align to the union-bbox midpoint', () => {
|
|
174
|
-
const rects = [kr('a', 0, 0, 100, 100), kr('b', 300, 0, 100, 100)];
|
|
175
|
-
// union bbox x: 0..400, cx=200
|
|
176
|
-
const centerX = computeAlign(rects, 'center-x');
|
|
177
|
-
expect(centerX).toEqual(
|
|
178
|
-
expect.arrayContaining([
|
|
179
|
-
{ key: 'a', x: 150, y: 0 },
|
|
180
|
-
{ key: 'b', x: 150, y: 0 },
|
|
181
|
-
])
|
|
182
|
-
);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test('no-op rects (already aligned) are omitted from the result', () => {
|
|
186
|
-
const rects = [kr('a', 0, 0), kr('b', 0, 50)];
|
|
187
|
-
expect(computeAlign(rects, 'left')).toEqual([]);
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
describe('computeDistribute', () => {
|
|
192
|
-
test('fewer than 3 rects → []', () => {
|
|
193
|
-
expect(computeDistribute([kr('a', 0, 0), kr('b', 200, 0)], 'x')).toEqual([]);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
test('holds first + last, spaces the middle evenly on x', () => {
|
|
197
|
-
// 0..100, then a bunched pair at 110/130, then 300..400 — total span
|
|
198
|
-
// first.x(0) to last.x+w(400) = 400, minus side-lengths(100+80+90+100=370) = 30 → gap 10
|
|
199
|
-
const rects = [
|
|
200
|
-
kr('a', 0, 0, 100, 50),
|
|
201
|
-
kr('b', 110, 0, 80, 50),
|
|
202
|
-
kr('c', 130, 0, 90, 50),
|
|
203
|
-
kr('d', 300, 0, 100, 50),
|
|
204
|
-
];
|
|
205
|
-
const out = computeDistribute(rects, 'x');
|
|
206
|
-
expect(out).toHaveLength(2); // only the 2 middle rects move
|
|
207
|
-
expect(out.find((m) => m.key === 'b')).toEqual({ key: 'b', x: 110, y: 0 });
|
|
208
|
-
expect(out.find((m) => m.key === 'c')).toEqual({ key: 'c', x: 200, y: 0 });
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
test('already-equal gaps still returns the (unchanged) middle positions', () => {
|
|
212
|
-
const rects = [kr('a', 0, 0), kr('b', 120, 0), kr('c', 240, 0)];
|
|
213
|
-
const out = computeDistribute(rects, 'x');
|
|
214
|
-
expect(out).toEqual([{ key: 'b', x: 120, y: 0 }]);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test('distributes on y', () => {
|
|
218
|
-
// y: 0..60, 90..150, 300..360 — total sides 180, span 360-180=180, gap 90
|
|
219
|
-
const rects = [kr('a', 0, 0, 50, 60), kr('b', 0, 90, 50, 60), kr('c', 0, 300, 50, 60)];
|
|
220
|
-
const out = computeDistribute(rects, 'y');
|
|
221
|
-
expect(out).toHaveLength(1);
|
|
222
|
-
expect(out[0]).toEqual({ key: 'b', x: 0, y: 150 });
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
describe('computeTidyGrid', () => {
|
|
227
|
-
test('fewer than 2 rects → []', () => {
|
|
228
|
-
expect(computeTidyGrid([kr('a', 5, 5)])).toEqual([]);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
test('4 rects → 2×2 grid, reading-order sorted, from the union-bbox origin', () => {
|
|
232
|
-
// scattered, deliberately out of reading order
|
|
233
|
-
const rects = [
|
|
234
|
-
kr('br', 500, 500, 50, 50),
|
|
235
|
-
kr('tl', 0, 0, 50, 50),
|
|
236
|
-
kr('tr', 400, 10, 50, 50),
|
|
237
|
-
kr('bl', 20, 490, 50, 50),
|
|
238
|
-
];
|
|
239
|
-
const out = computeTidyGrid(rects, { gap: 10 });
|
|
240
|
-
expect(out).toHaveLength(4);
|
|
241
|
-
const byKey = Object.fromEntries(out.map((m) => [m.key, m]));
|
|
242
|
-
// reading order (y then x): tl(0,0) → tr(400,10) → bl(20,490) → br(500,500)
|
|
243
|
-
// columns = round(sqrt(4)) = 2 → row0: tl,tr · row1: bl,br
|
|
244
|
-
expect(byKey.tl).toEqual({ key: 'tl', x: 0, y: 0 });
|
|
245
|
-
expect(byKey.tr).toEqual({ key: 'tr', x: 60, y: 0 }); // col0 width 50 + gap 10
|
|
246
|
-
expect(byKey.bl).toEqual({ key: 'bl', x: 0, y: 60 });
|
|
247
|
-
expect(byKey.br).toEqual({ key: 'br', x: 60, y: 60 });
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
test('honors an explicit column count', () => {
|
|
251
|
-
const rects = [kr('a', 0, 0, 40, 20), kr('b', 0, 0, 40, 20), kr('c', 0, 0, 40, 20)];
|
|
252
|
-
const out = computeTidyGrid(rects, { gap: 5, columns: 1 });
|
|
253
|
-
// 1 column → stacked vertically
|
|
254
|
-
expect(out.map((m) => m.x)).toEqual([0, 0, 0]);
|
|
255
|
-
expect(out.map((m) => m.y)).toEqual([0, 25, 50]);
|
|
256
|
-
});
|
|
257
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
// export-assemble.test.ts — DDR-231: the in-cell half of the pptx browser
|
|
2
|
-
// lane. assemblePngDeck composes client-captured PNG bytes into a deck with
|
|
3
|
-
// PptxGenJS — pure JS over pure data (the zip containment class), so it must
|
|
4
|
-
// work with no browser, no filesystem inputs, no tenant checkout.
|
|
5
|
-
|
|
6
|
-
import { describe, expect, test } from 'bun:test';
|
|
7
|
-
|
|
8
|
-
import { assemblePngDeck } from '../exporters/pptx.ts';
|
|
9
|
-
|
|
10
|
-
// 1×1 transparent PNG.
|
|
11
|
-
const PNG_1PX = Uint8Array.from(
|
|
12
|
-
atob(
|
|
13
|
-
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='
|
|
14
|
-
),
|
|
15
|
-
(c) => c.charCodeAt(0)
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
describe('assemblePngDeck — bytes in, deck out', () => {
|
|
19
|
-
test('two PNGs become a two-slide .pptx (zip container with per-slide parts)', async () => {
|
|
20
|
-
const body = await assemblePngDeck([PNG_1PX, PNG_1PX], 3);
|
|
21
|
-
// .pptx IS a zip — PK\x03\x04 magic.
|
|
22
|
-
expect(body[0]).toBe(0x50);
|
|
23
|
-
expect(body[1]).toBe(0x4b);
|
|
24
|
-
const JSZip = (await import('jszip')).default;
|
|
25
|
-
const zip = await JSZip.loadAsync(body);
|
|
26
|
-
expect(zip.file('ppt/slides/slide1.xml')).toBeTruthy();
|
|
27
|
-
expect(zip.file('ppt/slides/slide2.xml')).toBeTruthy();
|
|
28
|
-
expect(zip.file('ppt/slides/slide3.xml')).toBeFalsy();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('non-PNG bytes are refused, not composed into a corrupt deck', async () => {
|
|
32
|
-
await expect(assemblePngDeck([new TextEncoder().encode('<svg/>')], 3)).rejects.toThrow(
|
|
33
|
-
/not a PNG/
|
|
34
|
-
);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('a \\x89PNG prefix on non-PNG bytes is caught by the full 8-byte signature (L3)', async () => {
|
|
38
|
-
// First 4 bytes match the old check, bytes 4-7 do not.
|
|
39
|
-
const fake = new Uint8Array(24);
|
|
40
|
-
fake.set([0x89, 0x50, 0x4e, 0x47, 0x00, 0x00, 0x00, 0x00], 0);
|
|
41
|
-
await expect(assemblePngDeck([fake], 3)).rejects.toThrow(/not a PNG/);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('an implausible IHDR dimension is refused, not fed to the geometry math (F3)', async () => {
|
|
45
|
-
// Valid 8-byte sig + IHDR claiming 0x7fffffff × 0x7fffffff.
|
|
46
|
-
const bomb = new Uint8Array(24);
|
|
47
|
-
bomb.set([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], 0);
|
|
48
|
-
const dv = new DataView(bomb.buffer);
|
|
49
|
-
dv.setUint32(16, 0x7fffffff);
|
|
50
|
-
dv.setUint32(20, 0x7fffffff);
|
|
51
|
-
await expect(assemblePngDeck([bomb], 3)).rejects.toThrow(/implausible PNG dimensions/);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
// export-browser-lane.test.ts — DDR-231 lane-resolution matrix for the client
|
|
2
|
-
// half of the hybrid export lanes (client/export-lane.js). Fail-first: red
|
|
3
|
-
// before the browser lane existed (the module didn't).
|
|
4
|
-
|
|
5
|
-
import { describe, expect, test } from 'bun:test';
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
BROWSER_CAPTURE_FORMATS,
|
|
9
|
-
browserCaptureEligible,
|
|
10
|
-
captureScale,
|
|
11
|
-
MAX_CAPTURE_ITEMS,
|
|
12
|
-
sanitizeCapturedItems,
|
|
13
|
-
} from '../client/export-lane.js';
|
|
14
|
-
|
|
15
|
-
const PNG_1PX = Uint8Array.from(
|
|
16
|
-
atob(
|
|
17
|
-
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='
|
|
18
|
-
),
|
|
19
|
-
(c) => c.charCodeAt(0)
|
|
20
|
-
);
|
|
21
|
-
const pngBlob = () => new Blob([PNG_1PX], { type: 'image/png' });
|
|
22
|
-
|
|
23
|
-
describe('browserCaptureEligible — who captures where', () => {
|
|
24
|
-
const base = { format: 'png', scope: 'artboard', artboardId: 'board-1' };
|
|
25
|
-
|
|
26
|
-
test('desktop/local NEVER routes through the browser bridge (playwright spine owns it)', () => {
|
|
27
|
-
expect(browserCaptureEligible({ ...base, exportLane: 'local' })).toBe(false);
|
|
28
|
-
expect(browserCaptureEligible({ ...base, exportLane: undefined })).toBe(false);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('a workspace WITH a render service still prefers the browser for png/svg artboard', () => {
|
|
32
|
-
expect(browserCaptureEligible({ ...base, exportLane: 'remote' })).toBe(true);
|
|
33
|
-
expect(browserCaptureEligible({ ...base, exportLane: 'remote', format: 'svg' })).toBe(true);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test('a workspace WITHOUT a render service gets png/svg artboard too (the DDR-231 win)', () => {
|
|
37
|
-
expect(browserCaptureEligible({ ...base, exportLane: 'none' })).toBe(true);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('video formats stay on the jobs lane (Canva/Kapwing pattern — full-CSS fidelity)', () => {
|
|
41
|
-
for (const format of ['mp4', 'webm', 'gif', 'pdf', 'html', 'zip', 'canva']) {
|
|
42
|
-
expect(browserCaptureEligible({ ...base, exportLane: 'remote', format })).toBe(false);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('the pptx deck rides the browser lane on its (only) canvas-as-separate scope', () => {
|
|
47
|
-
const deck = { format: 'pptx', scope: 'canvas-as-separate' };
|
|
48
|
-
expect(browserCaptureEligible({ ...deck, exportLane: 'remote' })).toBe(true);
|
|
49
|
-
expect(browserCaptureEligible({ ...deck, exportLane: 'none' })).toBe(true);
|
|
50
|
-
expect(browserCaptureEligible({ ...deck, exportLane: 'local' })).toBe(false);
|
|
51
|
-
expect(
|
|
52
|
-
browserCaptureEligible({ format: 'pptx', scope: 'artboard', exportLane: 'remote' })
|
|
53
|
-
).toBe(false);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('non-artboard scopes stay on the jobs lane (v1: capture what you are looking at)', () => {
|
|
57
|
-
for (const scope of ['selection', 'canvas-as-separate', 'project-raw']) {
|
|
58
|
-
expect(browserCaptureEligible({ ...base, exportLane: 'remote', scope })).toBe(false);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('no known artboard → no browser capture (the bridge selects by id)', () => {
|
|
63
|
-
expect(browserCaptureEligible({ ...base, exportLane: 'remote', artboardId: null })).toBe(false);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('the format set matches what canvas-lib can actually produce', () => {
|
|
67
|
-
expect([...BROWSER_CAPTURE_FORMATS].sort()).toEqual(['png', 'svg']);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('sanitizeCapturedItems — the untrusted-reply neutralizer (F1)', () => {
|
|
72
|
-
test('a forged non-PNG blob named .png is REJECTED by magic sniffing', async () => {
|
|
73
|
-
const evil = [
|
|
74
|
-
{ name: 'invoice.png', blob: new Blob(['<script>alert(1)</script>'], { type: 'image/png' }) },
|
|
75
|
-
];
|
|
76
|
-
await expect(sanitizeCapturedItems(evil, 'png')).rejects.toThrow(/not a valid png/);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test('a real PNG passes and gets a path-stripped name + forced MIME', async () => {
|
|
80
|
-
const [safe] = await sanitizeCapturedItems(
|
|
81
|
-
[{ name: '../../etc/passwd\r\n.png', blob: pngBlob() }],
|
|
82
|
-
'png'
|
|
83
|
-
);
|
|
84
|
-
expect(safe.name).not.toContain('/');
|
|
85
|
-
expect(safe.name).not.toContain('..');
|
|
86
|
-
expect(safe.name.endsWith('.png')).toBe(true);
|
|
87
|
-
expect(safe.blob.type).toBe('image/png');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('svg magic (<svg / <?xml) is accepted; other bytes rejected', async () => {
|
|
91
|
-
const [ok] = await sanitizeCapturedItems(
|
|
92
|
-
[{ name: 'b.svg', blob: new Blob(['<svg/>']) }],
|
|
93
|
-
'svg'
|
|
94
|
-
);
|
|
95
|
-
expect(ok.name).toBe('b.svg');
|
|
96
|
-
expect(ok.blob.type).toBe('image/svg+xml');
|
|
97
|
-
await expect(
|
|
98
|
-
sanitizeCapturedItems([{ name: 'x.svg', blob: new Blob(['not svg']) }], 'svg')
|
|
99
|
-
).rejects.toThrow(/not a valid svg/);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('too many items is refused (a forged reply cannot drive N downloads)', async () => {
|
|
103
|
-
const many = Array.from({ length: MAX_CAPTURE_ITEMS + 1 }, () => ({ blob: pngBlob() }));
|
|
104
|
-
await expect(sanitizeCapturedItems(many, 'png')).rejects.toThrow(/too many items/);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('a non-blob item is refused', async () => {
|
|
108
|
-
await expect(
|
|
109
|
-
sanitizeCapturedItems([{ name: 'x.png', blob: 'not a blob' as unknown as Blob }], 'png')
|
|
110
|
-
).rejects.toThrow(/not a blob/);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe('captureScale — dpi folds over scale, clamped to the raster guard window', () => {
|
|
115
|
-
test('scale multiplier passes through', () => {
|
|
116
|
-
expect(captureScale({ scale: 2 })).toBe(2);
|
|
117
|
-
});
|
|
118
|
-
test('dpi wins over scale (mirrors exporters/png.ts resolveDeviceScale)', () => {
|
|
119
|
-
expect(captureScale({ dpi: 300, scale: 2 })).toBe(300 / 96);
|
|
120
|
-
});
|
|
121
|
-
test('clamped to [1, 8]', () => {
|
|
122
|
-
expect(captureScale({ dpi: 1200 })).toBe(8);
|
|
123
|
-
expect(captureScale({ scale: 0 })).toBe(1);
|
|
124
|
-
expect(captureScale()).toBe(1);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
// export-capture-fidelity.test.ts — DDR-231 T6: the fidelity gate for the
|
|
2
|
-
// browser export lane.
|
|
3
|
-
//
|
|
4
|
-
// The browser lane rasterizes dom-to-svg output (capture-core.ts); the
|
|
5
|
-
// reference spine screenshots the live element (the playwright path the
|
|
6
|
-
// desktop + render worker use). dom-to-svg RECONSTRUCTS the DOM as SVG
|
|
7
|
-
// primitives, so the two are not byte-identical — this gate pins how far
|
|
8
|
-
// apart they may drift on a representative artboard (tokens-styled text,
|
|
9
|
-
// gradient, border-radius, shadow, nested layout). A capture regression
|
|
10
|
-
// (dropped background, missing text, wrong geometry) blows well past these
|
|
11
|
-
// thresholds; anti-aliasing and shadow-softness differences do not.
|
|
12
|
-
//
|
|
13
|
-
// Runs a real Chromium (repo playwright) — lives in the studio suite, not the
|
|
14
|
-
// sync lane.
|
|
15
|
-
|
|
16
|
-
import { describe, expect, test } from 'bun:test';
|
|
17
|
-
|
|
18
|
-
import { getBrowserBundle, getCaptureCoreBundle } from '../exporters/_browser-bundles.ts';
|
|
19
|
-
|
|
20
|
-
const FIXTURE = `<!doctype html><html><body style="margin:0;background:#222">
|
|
21
|
-
<div class="dc-world">
|
|
22
|
-
<div data-dc-screen="board-1" style="position:absolute;left:0;top:0;width:420px;height:300px;
|
|
23
|
-
background:linear-gradient(135deg,#1c2340,#3a1f47);border-radius:16px;overflow:hidden;
|
|
24
|
-
font-family:Arial,Helvetica,sans-serif;color:#f5f2ea">
|
|
25
|
-
<div style="padding:28px">
|
|
26
|
-
<div style="font-size:24px;font-weight:700;letter-spacing:-0.4px">Summer Camp</div>
|
|
27
|
-
<div style="font-size:13px;opacity:.75;margin-top:6px">Brno · 12–16 August</div>
|
|
28
|
-
<div style="display:flex;gap:10px;margin-top:18px">
|
|
29
|
-
<div style="flex:1;height:64px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15);border-radius:10px"></div>
|
|
30
|
-
<div style="flex:1;height:64px;background:#e4572e;border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.4)"></div>
|
|
31
|
-
</div>
|
|
32
|
-
<div style="margin-top:16px;font-size:12px;line-height:1.5;opacity:.85">
|
|
33
|
-
Registrace do konce července. Kapacita 60 hráčů.
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
</div></body></html>`;
|
|
38
|
-
|
|
39
|
-
describe('browser-lane capture vs playwright reference', () => {
|
|
40
|
-
test(
|
|
41
|
-
'raster stays within the drift thresholds on a representative artboard',
|
|
42
|
-
async () => {
|
|
43
|
-
const [iife, core] = await Promise.all([
|
|
44
|
-
getBrowserBundle('dom-to-svg', 'domToSvg'),
|
|
45
|
-
getCaptureCoreBundle(),
|
|
46
|
-
]);
|
|
47
|
-
const { launchChromium } = (await import('../bin/_pw-launch.mjs')) as {
|
|
48
|
-
launchChromium: () => Promise<{
|
|
49
|
-
newContext: (o: unknown) => Promise<{ newPage: () => Promise<unknown> }>;
|
|
50
|
-
close: () => Promise<void>;
|
|
51
|
-
}>;
|
|
52
|
-
};
|
|
53
|
-
const browser = await launchChromium();
|
|
54
|
-
try {
|
|
55
|
-
const ctx = await browser.newContext({
|
|
56
|
-
viewport: { width: 800, height: 600 },
|
|
57
|
-
deviceScaleFactor: 1,
|
|
58
|
-
});
|
|
59
|
-
// biome-ignore lint/suspicious/noExplicitAny: playwright page surface
|
|
60
|
-
const page: any = await ctx.newPage();
|
|
61
|
-
await page.setContent(FIXTURE);
|
|
62
|
-
await page.evaluate(() => document.fonts.ready);
|
|
63
|
-
await page.addScriptTag({ path: iife });
|
|
64
|
-
await page.addScriptTag({ path: core, type: 'module' });
|
|
65
|
-
await page.waitForFunction(
|
|
66
|
-
() => !!(window as never as Record<string, unknown>)['__maudeCaptureCore']
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
// Reference: the playwright spine's capture of the same element.
|
|
70
|
-
const el = page.locator('[data-dc-screen]');
|
|
71
|
-
const refBytes: Uint8Array = await el.screenshot();
|
|
72
|
-
const refB64 = Buffer.from(refBytes).toString('base64');
|
|
73
|
-
|
|
74
|
-
const verdict = await page.evaluate(async (refDataUri: string) => {
|
|
75
|
-
// biome-ignore lint/suspicious/noExplicitAny: injected globals
|
|
76
|
-
const w = window as any;
|
|
77
|
-
const core = w.__maudeCaptureCore;
|
|
78
|
-
const target = document.querySelector('[data-dc-screen]') as Element;
|
|
79
|
-
const svg = await core.svgForElement(target, w.domToSvg);
|
|
80
|
-
const blob: Blob = await core.rasterizeSvg(svg, 1);
|
|
81
|
-
|
|
82
|
-
const load = (src: string) =>
|
|
83
|
-
new Promise<HTMLImageElement>((resolve, reject) => {
|
|
84
|
-
const img = new Image();
|
|
85
|
-
img.onload = () => resolve(img);
|
|
86
|
-
img.onerror = () => reject(new Error('decode failed'));
|
|
87
|
-
img.src = src;
|
|
88
|
-
});
|
|
89
|
-
const candUrl = URL.createObjectURL(blob);
|
|
90
|
-
const [ref, cand] = await Promise.all([load(refDataUri), load(candUrl)]);
|
|
91
|
-
const W = ref.naturalWidth;
|
|
92
|
-
const H = ref.naturalHeight;
|
|
93
|
-
const draw = (img: HTMLImageElement) => {
|
|
94
|
-
const c = document.createElement('canvas');
|
|
95
|
-
c.width = W;
|
|
96
|
-
c.height = H;
|
|
97
|
-
const g = c.getContext('2d') as CanvasRenderingContext2D;
|
|
98
|
-
g.drawImage(img, 0, 0, W, H);
|
|
99
|
-
return g.getImageData(0, 0, W, H).data;
|
|
100
|
-
};
|
|
101
|
-
const a = draw(ref);
|
|
102
|
-
const b = draw(cand);
|
|
103
|
-
let sum = 0;
|
|
104
|
-
let close = 0;
|
|
105
|
-
const n = W * H;
|
|
106
|
-
for (let i = 0; i < n * 4; i += 4) {
|
|
107
|
-
const d =
|
|
108
|
-
(Math.abs(a[i] - b[i]) +
|
|
109
|
-
Math.abs(a[i + 1] - b[i + 1]) +
|
|
110
|
-
Math.abs(a[i + 2] - b[i + 2])) /
|
|
111
|
-
3;
|
|
112
|
-
sum += d;
|
|
113
|
-
if (d <= 32) close += 1;
|
|
114
|
-
}
|
|
115
|
-
URL.revokeObjectURL(candUrl);
|
|
116
|
-
return {
|
|
117
|
-
refW: W,
|
|
118
|
-
refH: H,
|
|
119
|
-
candW: cand.naturalWidth,
|
|
120
|
-
candH: cand.naturalHeight,
|
|
121
|
-
meanDelta: sum / n,
|
|
122
|
-
closeRatio: close / n,
|
|
123
|
-
};
|
|
124
|
-
}, `data:image/png;base64,${refB64}`);
|
|
125
|
-
|
|
126
|
-
// Geometry must agree exactly — a size mismatch is a broken capture,
|
|
127
|
-
// not drift.
|
|
128
|
-
expect(verdict.candW).toBe(verdict.refW);
|
|
129
|
-
expect(verdict.candH).toBe(verdict.refH);
|
|
130
|
-
// Thresholds set from the MEASURED baseline (fail-first evidence,
|
|
131
|
-
// 2026-08-23): healthy capture = meanDelta 1.48, closeRatio 1.0;
|
|
132
|
-
// a simulated dropped-text regression = meanDelta 5.37, closeRatio
|
|
133
|
-
// 0.974. Both captures render in the SAME engine, so platform font
|
|
134
|
-
// variance cancels — the residual is dom-to-svg reconstruction only.
|
|
135
|
-
// meanDelta < 4 / closeRatio > 0.99 keeps ~2.7× headroom over healthy
|
|
136
|
-
// while catching the text-drop class (mean-only thresholds missed it —
|
|
137
|
-
// text is a small pixel fraction, hence the paired closeRatio gate).
|
|
138
|
-
expect(verdict.meanDelta).toBeLessThan(4);
|
|
139
|
-
expect(verdict.closeRatio).toBeGreaterThan(0.99);
|
|
140
|
-
} finally {
|
|
141
|
-
await browser.close();
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
|
-
{ timeout: 60_000 }
|
|
145
|
-
);
|
|
146
|
-
});
|