@1agh/maude 1.0.9 → 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/probe.ts +268 -16
- 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/ReadinessList.jsx +7 -1
- 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/readiness.ts +42 -7
- 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 +91 -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 -301
- 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/fake-claude-auth.mjs +0 -13
- 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/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/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 -226
- 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,63 +0,0 @@
|
|
|
1
|
-
// Unit: helpers exported by ws.ts that gate the Phase 8 collab endpoint.
|
|
2
|
-
// Pure functions — no Bun.serve / no fixtures needed. The full integration
|
|
3
|
-
// (host-header rejection at the upgrade layer) is covered by the manual
|
|
4
|
-
// curl smoke in the Task 1 validate row.
|
|
5
|
-
|
|
6
|
-
import { describe, expect, test } from 'bun:test';
|
|
7
|
-
|
|
8
|
-
import { isLoopbackHost, parseCollabSlug } from '../ws.ts';
|
|
9
|
-
|
|
10
|
-
describe('parseCollabSlug', () => {
|
|
11
|
-
test('extracts slug from /_ws/collab/<slug>', () => {
|
|
12
|
-
expect(parseCollabSlug('/_ws/collab/foo')).toBe('foo');
|
|
13
|
-
expect(parseCollabSlug('/_ws/collab/ui-home_screen')).toBe('ui-home_screen');
|
|
14
|
-
expect(parseCollabSlug('/_ws/collab/A1B2c3')).toBe('A1B2c3');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test('rejects non-collab paths', () => {
|
|
18
|
-
expect(parseCollabSlug('/_ws')).toBeNull();
|
|
19
|
-
expect(parseCollabSlug('/_ws/inspector')).toBeNull();
|
|
20
|
-
expect(parseCollabSlug('/_ws/collab')).toBeNull();
|
|
21
|
-
expect(parseCollabSlug('/_ws/collab/')).toBeNull();
|
|
22
|
-
expect(parseCollabSlug('/api/comments')).toBeNull();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('rejects slugs with disallowed chars (URL-encoded, slashes, dots)', () => {
|
|
26
|
-
expect(parseCollabSlug('/_ws/collab/foo/bar')).toBeNull();
|
|
27
|
-
expect(parseCollabSlug('/_ws/collab/foo.bar')).toBeNull();
|
|
28
|
-
expect(parseCollabSlug('/_ws/collab/foo%20bar')).toBeNull();
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe('isLoopbackHost', () => {
|
|
33
|
-
test('accepts loopback aliases with or without port', () => {
|
|
34
|
-
expect(isLoopbackHost('127.0.0.1:4399')).toBe(true);
|
|
35
|
-
expect(isLoopbackHost('127.0.0.1')).toBe(true);
|
|
36
|
-
expect(isLoopbackHost('localhost:4399')).toBe(true);
|
|
37
|
-
expect(isLoopbackHost('localhost')).toBe(true);
|
|
38
|
-
expect(isLoopbackHost('[::1]:4399')).toBe(true);
|
|
39
|
-
expect(isLoopbackHost('[::1]')).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('case-insensitive', () => {
|
|
43
|
-
expect(isLoopbackHost('LOCALHOST:4399')).toBe(true);
|
|
44
|
-
expect(isLoopbackHost('LocalHost')).toBe(true);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('rejects non-loopback hosts', () => {
|
|
48
|
-
expect(isLoopbackHost('example.com')).toBe(false);
|
|
49
|
-
expect(isLoopbackHost('example.com:4399')).toBe(false);
|
|
50
|
-
expect(isLoopbackHost('10.0.0.1:4399')).toBe(false);
|
|
51
|
-
expect(isLoopbackHost('192.168.1.1')).toBe(false);
|
|
52
|
-
expect(isLoopbackHost('host.docker.internal')).toBe(false);
|
|
53
|
-
// Common attacker patterns — host header smuggling tries.
|
|
54
|
-
expect(isLoopbackHost('127.0.0.1.attacker.com')).toBe(false);
|
|
55
|
-
expect(isLoopbackHost('localhost.attacker.com')).toBe(false);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('handles missing / empty host gracefully', () => {
|
|
59
|
-
expect(isLoopbackHost(null)).toBe(false);
|
|
60
|
-
expect(isLoopbackHost('')).toBe(false);
|
|
61
|
-
expect(isLoopbackHost(' ')).toBe(false);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
// Unit: the DDR-122 follow-up origin gate — untrusted canvas-realm doc ops may
|
|
2
|
-
// never write the canvas SOURCE lanes (html / css / meta / syncMeta), while the
|
|
3
|
-
// lanes the canvas legitimately co-authors (comments / annotations /
|
|
4
|
-
// presentation) keep working for same-machine boards.
|
|
5
|
-
//
|
|
6
|
-
// Cloud Phase 1 Task 3. See apps/studio/collab/origins.ts for the threat model
|
|
7
|
-
// and for why the gate needs a mirror doc rather than update-byte inspection.
|
|
8
|
-
|
|
9
|
-
import { describe, expect, test } from 'bun:test';
|
|
10
|
-
|
|
11
|
-
import * as encoding from 'lib0/encoding';
|
|
12
|
-
import { writeUpdate } from 'y-protocols/sync';
|
|
13
|
-
import * as Y from 'yjs';
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
isBodyLane,
|
|
17
|
-
isCanvasAuthorableLane,
|
|
18
|
-
isTrustedOrigin,
|
|
19
|
-
laneVocabulary,
|
|
20
|
-
markTrustedOrigin,
|
|
21
|
-
rootTypesTouched,
|
|
22
|
-
SHELL_EDIT_ORIGIN,
|
|
23
|
-
touchesBodyLane,
|
|
24
|
-
} from '../collab/origins.ts';
|
|
25
|
-
import { MESSAGE_SYNC } from '../collab/protocol.ts';
|
|
26
|
-
import { createRoom, type RoomCallbacks, type RoomConn } from '../collab/room.ts';
|
|
27
|
-
|
|
28
|
-
const MESSAGE_SYNC_UPDATE = 2; // y-protocols/sync messageYjsUpdate
|
|
29
|
-
|
|
30
|
-
function makeConn(id: string, realm?: 'main' | 'canvas'): RoomConn & { recv: Uint8Array[] } {
|
|
31
|
-
const recv: Uint8Array[] = [];
|
|
32
|
-
return {
|
|
33
|
-
id,
|
|
34
|
-
...(realm ? { realm } : {}),
|
|
35
|
-
send(payload: Uint8Array) {
|
|
36
|
-
recv.push(payload);
|
|
37
|
-
},
|
|
38
|
-
recv,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function makeCallbacks(): RoomCallbacks {
|
|
43
|
-
return {
|
|
44
|
-
seed() {
|
|
45
|
-
/* nothing on disk */
|
|
46
|
-
},
|
|
47
|
-
persistJson() {
|
|
48
|
-
/* no-op */
|
|
49
|
-
},
|
|
50
|
-
persistBinary() {
|
|
51
|
-
/* no-op */
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Frame a raw Y update as a y-websocket MESSAGE_SYNC / update frame. */
|
|
57
|
-
function syncUpdateFrame(update: Uint8Array): Uint8Array {
|
|
58
|
-
const encoder = encoding.createEncoder();
|
|
59
|
-
encoding.writeVarUint(encoder, MESSAGE_SYNC);
|
|
60
|
-
writeUpdate(encoder, update);
|
|
61
|
-
return encoding.toUint8Array(encoder);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Build the frame a malicious (or merely careless) canvas peer would send: it
|
|
66
|
-
* mirrors the room's current state, mutates locally, and ships the delta —
|
|
67
|
-
* exactly what `use-collab.tsx` does, minus lock 1.
|
|
68
|
-
*/
|
|
69
|
-
function peerDelta(room: { doc: Y.Doc }, mutate: (doc: Y.Doc) => void): Uint8Array {
|
|
70
|
-
const peer = new Y.Doc();
|
|
71
|
-
Y.applyUpdate(peer, Y.encodeStateAsUpdate(room.doc));
|
|
72
|
-
const before = Y.encodeStateVector(peer);
|
|
73
|
-
mutate(peer);
|
|
74
|
-
return Y.encodeStateAsUpdate(peer, before);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
describe('origins — lane vocabulary + trusted sentinels', () => {
|
|
78
|
-
test('body lanes and canvas-authorable lanes are disjoint and complete', () => {
|
|
79
|
-
const { body, canvasAuthorable } = laneVocabulary();
|
|
80
|
-
expect(body).toEqual(['css', 'html', 'meta', 'syncMeta']);
|
|
81
|
-
expect(canvasAuthorable).toEqual(['annotations', 'comments', 'presentation']);
|
|
82
|
-
for (const lane of body) {
|
|
83
|
-
expect(isBodyLane(lane)).toBe(true);
|
|
84
|
-
expect(isCanvasAuthorableLane(lane)).toBe(false);
|
|
85
|
-
}
|
|
86
|
-
for (const lane of canvasAuthorable) {
|
|
87
|
-
expect(isBodyLane(lane)).toBe(false);
|
|
88
|
-
expect(isCanvasAuthorableLane(lane)).toBe(true);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('a forged origin object is NOT trusted; only the real sentinel is', () => {
|
|
93
|
-
expect(isTrustedOrigin(SHELL_EDIT_ORIGIN)).toBe(true);
|
|
94
|
-
// The exact shape of the sentinel, rebuilt by an attacker who read the source.
|
|
95
|
-
expect(isTrustedOrigin({ maudeOrigin: 'shell-edit' })).toBe(false);
|
|
96
|
-
expect(isTrustedOrigin('shell-edit')).toBe(false);
|
|
97
|
-
expect(isTrustedOrigin(null)).toBe(false);
|
|
98
|
-
expect(isTrustedOrigin(undefined)).toBe(false);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('markTrustedOrigin registers a frozen object and returns it', () => {
|
|
102
|
-
const sentinel = markTrustedOrigin(Object.freeze({ maudeOrigin: 'test' }));
|
|
103
|
-
expect(isTrustedOrigin(sentinel)).toBe(true);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test('rootTypesTouched resolves nested types up to their root', () => {
|
|
107
|
-
const doc = new Y.Doc();
|
|
108
|
-
const seen: Set<string>[] = [];
|
|
109
|
-
doc.on('afterTransaction', (tr: Y.Transaction) => {
|
|
110
|
-
if (tr.origin === 'probe') seen.push(rootTypesTouched(tr));
|
|
111
|
-
});
|
|
112
|
-
doc.transact(() => {
|
|
113
|
-
const nested = new Y.Map<string>();
|
|
114
|
-
doc.getArray<Y.Map<string>>('comments').push([nested]);
|
|
115
|
-
nested.set('text', 'hi');
|
|
116
|
-
}, 'probe');
|
|
117
|
-
expect(seen.length).toBe(1);
|
|
118
|
-
expect([...(seen[0] ?? [])]).toEqual(['comments']);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('touchesBodyLane discriminates body writes from comment writes', () => {
|
|
122
|
-
const doc = new Y.Doc();
|
|
123
|
-
const verdicts: boolean[] = [];
|
|
124
|
-
doc.on('afterTransaction', (tr: Y.Transaction) => {
|
|
125
|
-
if (tr.origin === 'probe') verdicts.push(touchesBodyLane(tr));
|
|
126
|
-
});
|
|
127
|
-
doc.transact(() => doc.getArray('comments').push([{ id: 'c1' }]), 'probe');
|
|
128
|
-
doc.transact(() => doc.getText('html').insert(0, '<b>x</b>'), 'probe');
|
|
129
|
-
// An insert into EXISTING text — the case whose target lane is absent from
|
|
130
|
-
// the update bytes (origins.ts § "why a mirror doc").
|
|
131
|
-
doc.transact(() => doc.getText('html').insert(3, 'evil'), 'probe');
|
|
132
|
-
expect(verdicts).toEqual([false, true, true]);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
describe('room origin gate — canvas realm', () => {
|
|
137
|
-
test('REJECTS a canvas-realm write to the body lane; doc untouched, not broadcast', async () => {
|
|
138
|
-
const room = createRoom('ui-screen', makeCallbacks());
|
|
139
|
-
const shell = makeConn('shell', 'main');
|
|
140
|
-
const canvas = makeConn('canvas', 'canvas');
|
|
141
|
-
await room.connect(shell);
|
|
142
|
-
await room.connect(canvas);
|
|
143
|
-
shell.recv.length = 0;
|
|
144
|
-
|
|
145
|
-
// Seed a body through the trusted path so the attack is an insert into
|
|
146
|
-
// EXISTING text — the case update-byte inspection cannot classify.
|
|
147
|
-
room.doc.transact(() => room.doc.getText('html').insert(0, '<main>ok</main>'), 'sync-agent');
|
|
148
|
-
shell.recv.length = 0;
|
|
149
|
-
canvas.recv.length = 0;
|
|
150
|
-
|
|
151
|
-
const evil = peerDelta(room, (d) => d.getText('html').insert(6, 'PWNED'));
|
|
152
|
-
room.receive(canvas, syncUpdateFrame(evil));
|
|
153
|
-
|
|
154
|
-
expect(room.doc.getText('html').toString()).toBe('<main>ok</main>');
|
|
155
|
-
expect(room.gateRefusals()).toBe(1);
|
|
156
|
-
// Nothing reached the other peer.
|
|
157
|
-
expect(shell.recv.length).toBe(0);
|
|
158
|
-
// The refused peer got a corrective full-state frame instead.
|
|
159
|
-
expect(canvas.recv.length).toBe(1);
|
|
160
|
-
await room.destroy();
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test('REJECTS canvas-realm writes to css, meta and syncMeta too', async () => {
|
|
164
|
-
for (const [lane, mutate] of [
|
|
165
|
-
['css', (d: Y.Doc) => d.getText('css').insert(0, '.evil{}')],
|
|
166
|
-
['meta', (d: Y.Doc) => d.getText('meta').insert(0, '{"evil":1}')],
|
|
167
|
-
['syncMeta', (d: Y.Doc) => d.getMap('syncMeta').set('bodyEditAt', 1)],
|
|
168
|
-
] as const) {
|
|
169
|
-
const room = createRoom(`lane-${lane}`, makeCallbacks());
|
|
170
|
-
const canvas = makeConn('canvas', 'canvas');
|
|
171
|
-
await room.connect(canvas);
|
|
172
|
-
room.receive(canvas, syncUpdateFrame(peerDelta(room, mutate)));
|
|
173
|
-
expect(room.gateRefusals()).toBe(1);
|
|
174
|
-
expect(Y.encodeStateAsUpdate(room.doc).length).toBe(
|
|
175
|
-
Y.encodeStateAsUpdate(new Y.Doc()).length
|
|
176
|
-
);
|
|
177
|
-
await room.destroy();
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test('ALLOWS canvas-realm comments + annotations (same-machine boards keep working)', async () => {
|
|
182
|
-
const room = createRoom('board', makeCallbacks());
|
|
183
|
-
const shell = makeConn('shell', 'main');
|
|
184
|
-
const canvas = makeConn('canvas', 'canvas');
|
|
185
|
-
await room.connect(shell);
|
|
186
|
-
await room.connect(canvas);
|
|
187
|
-
shell.recv.length = 0;
|
|
188
|
-
|
|
189
|
-
room.receive(
|
|
190
|
-
canvas,
|
|
191
|
-
syncUpdateFrame(
|
|
192
|
-
peerDelta(room, (d) => {
|
|
193
|
-
d.getArray('comments').push([{ id: 'c1', text: 'from the canvas' }]);
|
|
194
|
-
d.getMap('annotations').set('svg', '<svg><rect/></svg>');
|
|
195
|
-
})
|
|
196
|
-
)
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
expect(room.doc.getArray('comments').toArray()).toEqual([
|
|
200
|
-
{ id: 'c1', text: 'from the canvas' },
|
|
201
|
-
]);
|
|
202
|
-
expect(room.doc.getMap('annotations').get('svg')).toBe('<svg><rect/></svg>');
|
|
203
|
-
expect(room.gateRefusals()).toBe(0);
|
|
204
|
-
// And it DID reach the other peer.
|
|
205
|
-
expect(shell.recv.length).toBeGreaterThan(0);
|
|
206
|
-
await room.destroy();
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
test('a mixed frame is refused WHOLESALE — no partial application of the good half', async () => {
|
|
210
|
-
const room = createRoom('mixed', makeCallbacks());
|
|
211
|
-
const canvas = makeConn('canvas', 'canvas');
|
|
212
|
-
await room.connect(canvas);
|
|
213
|
-
|
|
214
|
-
room.receive(
|
|
215
|
-
canvas,
|
|
216
|
-
syncUpdateFrame(
|
|
217
|
-
peerDelta(room, (d) => {
|
|
218
|
-
d.getArray('comments').push([{ id: 'decoy' }]);
|
|
219
|
-
d.getText('html').insert(0, 'PWNED');
|
|
220
|
-
})
|
|
221
|
-
)
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
expect(room.gateRefusals()).toBe(1);
|
|
225
|
-
expect(room.doc.getArray('comments').length).toBe(0);
|
|
226
|
-
expect(room.doc.getText('html').toString()).toBe('');
|
|
227
|
-
await room.destroy();
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
test('the gate SURVIVES a refusal — the next honest canvas frame still applies', async () => {
|
|
231
|
-
const room = createRoom('recover', makeCallbacks());
|
|
232
|
-
const canvas = makeConn('canvas', 'canvas');
|
|
233
|
-
await room.connect(canvas);
|
|
234
|
-
|
|
235
|
-
room.receive(canvas, syncUpdateFrame(peerDelta(room, (d) => d.getText('html').insert(0, 'x'))));
|
|
236
|
-
expect(room.gateRefusals()).toBe(1);
|
|
237
|
-
|
|
238
|
-
room.receive(
|
|
239
|
-
canvas,
|
|
240
|
-
syncUpdateFrame(peerDelta(room, (d) => d.getArray('comments').push([{ id: 'after' }])))
|
|
241
|
-
);
|
|
242
|
-
expect(room.gateRefusals()).toBe(1);
|
|
243
|
-
expect(room.doc.getArray('comments').toArray()).toEqual([{ id: 'after' }]);
|
|
244
|
-
await room.destroy();
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
test('the MAIN realm is ungated — the shell may still write the body', async () => {
|
|
248
|
-
const room = createRoom('shell-write', makeCallbacks());
|
|
249
|
-
const shell = makeConn('shell', 'main');
|
|
250
|
-
await room.connect(shell);
|
|
251
|
-
|
|
252
|
-
room.receive(shell, syncUpdateFrame(peerDelta(room, (d) => d.getText('html').insert(0, 'ok'))));
|
|
253
|
-
|
|
254
|
-
expect(room.doc.getText('html').toString()).toBe('ok');
|
|
255
|
-
expect(room.gateRefusals()).toBe(0);
|
|
256
|
-
await room.destroy();
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
test('a conn with no realm defaults to trusted (pre-existing callers unchanged)', async () => {
|
|
260
|
-
const room = createRoom('legacy', makeCallbacks());
|
|
261
|
-
const legacy = makeConn('legacy');
|
|
262
|
-
await room.connect(legacy);
|
|
263
|
-
|
|
264
|
-
room.receive(
|
|
265
|
-
legacy,
|
|
266
|
-
syncUpdateFrame(peerDelta(room, (d) => d.getText('html').insert(0, 'ok')))
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
expect(room.doc.getText('html').toString()).toBe('ok');
|
|
270
|
-
expect(room.gateRefusals()).toBe(0);
|
|
271
|
-
await room.destroy();
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
test('awareness frames from the canvas realm pass through the gate untouched', async () => {
|
|
275
|
-
const room = createRoom('presence', makeCallbacks());
|
|
276
|
-
const canvas = makeConn('canvas', 'canvas');
|
|
277
|
-
await room.connect(canvas);
|
|
278
|
-
|
|
279
|
-
// A well-formed awareness frame from a peer's own Awareness instance.
|
|
280
|
-
const peerDoc = new Y.Doc();
|
|
281
|
-
const { Awareness, encodeAwarenessUpdate } = await import('y-protocols/awareness');
|
|
282
|
-
const peerAwareness = new Awareness(peerDoc);
|
|
283
|
-
peerAwareness.setLocalState({ name: 'canvas peer' });
|
|
284
|
-
const encoder = encoding.createEncoder();
|
|
285
|
-
encoding.writeVarUint(encoder, 1 /* MESSAGE_AWARENESS */);
|
|
286
|
-
encoding.writeVarUint8Array(
|
|
287
|
-
encoder,
|
|
288
|
-
encodeAwarenessUpdate(peerAwareness, [peerAwareness.clientID])
|
|
289
|
-
);
|
|
290
|
-
room.receive(canvas, encoding.toUint8Array(encoder));
|
|
291
|
-
|
|
292
|
-
expect(room.awareness.getStates().get(peerAwareness.clientID)).toEqual({ name: 'canvas peer' });
|
|
293
|
-
expect(room.gateRefusals()).toBe(0);
|
|
294
|
-
await room.destroy();
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
test('a malformed sync frame from the canvas realm mutates nothing and leaves the room usable', async () => {
|
|
298
|
-
const room = createRoom('garbage', makeCallbacks());
|
|
299
|
-
const canvas = makeConn('canvas', 'canvas');
|
|
300
|
-
await room.connect(canvas);
|
|
301
|
-
|
|
302
|
-
const encoder = encoding.createEncoder();
|
|
303
|
-
encoding.writeVarUint(encoder, MESSAGE_SYNC);
|
|
304
|
-
encoding.writeVarUint(encoder, MESSAGE_SYNC_UPDATE);
|
|
305
|
-
encoding.writeVarUint8Array(encoder, new Uint8Array([0xff, 0xff, 0xff, 0xff]));
|
|
306
|
-
// yjs swallows an undecodable update internally rather than throwing, so
|
|
307
|
-
// this is not a *gate* refusal — it never becomes a transaction at all, on
|
|
308
|
-
// the mirror or on the room doc. Assert the property that matters: nothing
|
|
309
|
-
// applied, no crash, and the gate still works afterwards.
|
|
310
|
-
room.receive(canvas, encoding.toUint8Array(encoder));
|
|
311
|
-
|
|
312
|
-
expect(Y.encodeStateAsUpdate(room.doc).length).toBe(Y.encodeStateAsUpdate(new Y.Doc()).length);
|
|
313
|
-
|
|
314
|
-
room.receive(canvas, syncUpdateFrame(peerDelta(room, (d) => d.getText('html').insert(0, 'x'))));
|
|
315
|
-
expect(room.gateRefusals()).toBe(1);
|
|
316
|
-
room.receive(
|
|
317
|
-
canvas,
|
|
318
|
-
syncUpdateFrame(peerDelta(room, (d) => d.getArray('comments').push([{ id: 'ok' }])))
|
|
319
|
-
);
|
|
320
|
-
expect(room.doc.getArray('comments').toArray()).toEqual([{ id: 'ok' }]);
|
|
321
|
-
await room.destroy();
|
|
322
|
-
});
|
|
323
|
-
});
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
// Unit: y-websocket binary protocol round-trips. Validates the frames the
|
|
2
|
-
// dev-server emits actually decode against a vanilla yjs/y-protocols peer.
|
|
3
|
-
|
|
4
|
-
import { describe, expect, test } from 'bun:test';
|
|
5
|
-
|
|
6
|
-
import * as decoding from 'lib0/decoding';
|
|
7
|
-
import { Awareness } from 'y-protocols/awareness';
|
|
8
|
-
import * as Y from 'yjs';
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
encodeAwarenessFrame,
|
|
12
|
-
encodeHandshake,
|
|
13
|
-
encodeSyncUpdate,
|
|
14
|
-
handleMessage,
|
|
15
|
-
MESSAGE_AWARENESS,
|
|
16
|
-
MESSAGE_SYNC,
|
|
17
|
-
} from '../collab/protocol.ts';
|
|
18
|
-
|
|
19
|
-
function readType(frame: Uint8Array): number {
|
|
20
|
-
return decoding.readVarUint(decoding.createDecoder(frame));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe('encodeHandshake', () => {
|
|
24
|
-
test('always emits sync step 1 frame', () => {
|
|
25
|
-
const doc = new Y.Doc();
|
|
26
|
-
const aw = new Awareness(doc);
|
|
27
|
-
const frames = encodeHandshake(doc, aw);
|
|
28
|
-
expect(frames.length).toBeGreaterThanOrEqual(1);
|
|
29
|
-
// biome-ignore lint/style/noNonNullAssertion: length asserted above
|
|
30
|
-
expect(readType(frames[0]!)).toBe(MESSAGE_SYNC);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('appends awareness frame when awareness has state', () => {
|
|
34
|
-
const doc = new Y.Doc();
|
|
35
|
-
const aw = new Awareness(doc);
|
|
36
|
-
aw.setLocalState({ name: 'Alice', __connId: 'a' });
|
|
37
|
-
const frames = encodeHandshake(doc, aw);
|
|
38
|
-
expect(frames.length).toBe(2);
|
|
39
|
-
// biome-ignore lint/style/noNonNullAssertion: length asserted above
|
|
40
|
-
expect(readType(frames[0]!)).toBe(MESSAGE_SYNC);
|
|
41
|
-
// biome-ignore lint/style/noNonNullAssertion: length asserted above
|
|
42
|
-
expect(readType(frames[1]!)).toBe(MESSAGE_AWARENESS);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('omits awareness frame when awareness is empty', () => {
|
|
46
|
-
const doc = new Y.Doc();
|
|
47
|
-
const aw = new Awareness(doc);
|
|
48
|
-
aw.setLocalState(null); // clear default
|
|
49
|
-
const frames = encodeHandshake(doc, aw);
|
|
50
|
-
expect(frames.length).toBe(1);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
describe('encodeSyncUpdate', () => {
|
|
55
|
-
test('wraps a Y.Doc update bytes in a sync frame', () => {
|
|
56
|
-
const doc = new Y.Doc();
|
|
57
|
-
doc.getArray('comments').push([{ id: 'c1', text: 'hi' }]);
|
|
58
|
-
const update = Y.encodeStateAsUpdate(doc);
|
|
59
|
-
const frame = encodeSyncUpdate(update);
|
|
60
|
-
expect(readType(frame)).toBe(MESSAGE_SYNC);
|
|
61
|
-
// The wrapped update applied to a fresh doc should reproduce the state.
|
|
62
|
-
const replay = new Y.Doc();
|
|
63
|
-
// Strip the message-type byte and the sync-step opcode by re-running
|
|
64
|
-
// through handleMessage — that's the contract clients use too.
|
|
65
|
-
const aw = new Awareness(replay);
|
|
66
|
-
handleMessage(frame, replay, aw, { send() {} });
|
|
67
|
-
expect((replay.getArray('comments').get(0) as { text: string }).text).toBe('hi');
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('encodeAwarenessFrame', () => {
|
|
72
|
-
test('emits awareness type byte + payload that applies to another Awareness', () => {
|
|
73
|
-
const docA = new Y.Doc();
|
|
74
|
-
const awA = new Awareness(docA);
|
|
75
|
-
awA.setLocalState({ name: 'Alice', __connId: 'a' });
|
|
76
|
-
const frame = encodeAwarenessFrame(awA, [awA.clientID]);
|
|
77
|
-
expect(readType(frame)).toBe(MESSAGE_AWARENESS);
|
|
78
|
-
|
|
79
|
-
const docB = new Y.Doc();
|
|
80
|
-
const awB = new Awareness(docB);
|
|
81
|
-
// Drop B's default local state so we can assert exactly one foreign peer
|
|
82
|
-
// landed in B's awareness registry.
|
|
83
|
-
awB.setLocalState(null);
|
|
84
|
-
handleMessage(frame, docB, awB, { send() {} });
|
|
85
|
-
const alice = awB.getStates().get(awA.clientID) as
|
|
86
|
-
| { name: string; __connId: string }
|
|
87
|
-
| undefined;
|
|
88
|
-
expect(alice?.name).toBe('Alice');
|
|
89
|
-
expect(alice?.__connId).toBe('a');
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe('handleMessage', () => {
|
|
94
|
-
test('full bidirectional handshake converges two peers', () => {
|
|
95
|
-
// Peer A: has comments
|
|
96
|
-
const docA = new Y.Doc();
|
|
97
|
-
docA.getArray('comments').push([{ id: 'c1', text: 'A wrote this' }]);
|
|
98
|
-
const awA = new Awareness(docA);
|
|
99
|
-
|
|
100
|
-
// Peer B: empty
|
|
101
|
-
const docB = new Y.Doc();
|
|
102
|
-
const awB = new Awareness(docB);
|
|
103
|
-
|
|
104
|
-
// Full handshake = each peer sends its own sync step 1, the other responds
|
|
105
|
-
// with sync step 2 (the diff). y-websocket's reference impl does the same.
|
|
106
|
-
const aFrames = encodeHandshake(docA, awA);
|
|
107
|
-
const bFrames = encodeHandshake(docB, awB);
|
|
108
|
-
|
|
109
|
-
// Cross-feed.
|
|
110
|
-
for (const frame of bFrames) {
|
|
111
|
-
const reply = handleMessage(frame, docA, awA, { send() {} });
|
|
112
|
-
if (reply) handleMessage(reply, docB, awB, { send() {} });
|
|
113
|
-
}
|
|
114
|
-
for (const frame of aFrames) {
|
|
115
|
-
const reply = handleMessage(frame, docB, awB, { send() {} });
|
|
116
|
-
if (reply) handleMessage(reply, docA, awA, { send() {} });
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// After exchange B has A's comment.
|
|
120
|
-
expect(docB.getArray('comments').length).toBe(1);
|
|
121
|
-
const c = docB.getArray('comments').get(0) as { text: string };
|
|
122
|
-
expect(c.text).toBe('A wrote this');
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test('post-handshake encodeSyncUpdate propagates a single edit', () => {
|
|
126
|
-
const docA = new Y.Doc();
|
|
127
|
-
const docB = new Y.Doc();
|
|
128
|
-
// A makes an edit and emits the diff via encodeSyncUpdate.
|
|
129
|
-
docA.getArray('comments').push([{ id: 'c2', text: 'later edit' }]);
|
|
130
|
-
const frame = encodeSyncUpdate(Y.encodeStateAsUpdate(docA));
|
|
131
|
-
|
|
132
|
-
const awB = new Awareness(docB);
|
|
133
|
-
handleMessage(frame, docB, awB, { send() {} });
|
|
134
|
-
expect(docB.getArray('comments').length).toBe(1);
|
|
135
|
-
expect((docB.getArray('comments').get(0) as { text: string }).text).toBe('later edit');
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test('unknown message type is a noop (forward-compat)', () => {
|
|
139
|
-
const doc = new Y.Doc();
|
|
140
|
-
const aw = new Awareness(doc);
|
|
141
|
-
// Frame with message type 99 (unknown).
|
|
142
|
-
const frame = new Uint8Array([99]);
|
|
143
|
-
const reply = handleMessage(frame, doc, aw, { send() {} });
|
|
144
|
-
expect(reply).toBeNull();
|
|
145
|
-
});
|
|
146
|
-
});
|