@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,590 +0,0 @@
|
|
|
1
|
-
// The dev-server's Maude Cloud lane — Cloud Phase 23 C3 + the maude:// code
|
|
2
|
-
// attach (Phase 17). The control plane AND the cell are stubbed on one local
|
|
3
|
-
// server; the assertions are about custody and trust posture, not transport:
|
|
4
|
-
// the code is exchanged only against the CONFIGURED cloud address, the hub
|
|
5
|
-
// token lands in the credential store, and linkedHub is written token-free.
|
|
6
|
-
|
|
7
|
-
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
|
8
|
-
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
9
|
-
import { tmpdir } from 'node:os';
|
|
10
|
-
import { join } from 'node:path';
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
connectOutcomeNote,
|
|
14
|
-
isDisplayableUrl,
|
|
15
|
-
isLinkedProjectRow,
|
|
16
|
-
localIdentityHint,
|
|
17
|
-
parseDeepLink,
|
|
18
|
-
shareViewUrl,
|
|
19
|
-
} from '../client/panels/CloudBar.jsx';
|
|
20
|
-
|
|
21
|
-
let stub: ReturnType<typeof Bun.serve> | null = null;
|
|
22
|
-
let scratch = '';
|
|
23
|
-
const seen: string[] = [];
|
|
24
|
-
|
|
25
|
-
beforeAll(() => {
|
|
26
|
-
scratch = mkdtempSync(join(tmpdir(), 'maude-cloud-test-'));
|
|
27
|
-
stub = Bun.serve({
|
|
28
|
-
port: 0,
|
|
29
|
-
hostname: '127.0.0.1',
|
|
30
|
-
fetch: async (req) => {
|
|
31
|
-
const url = new URL(req.url);
|
|
32
|
-
seen.push(`${req.method} ${url.pathname}`);
|
|
33
|
-
const json = (b: unknown, status = 200) =>
|
|
34
|
-
new Response(JSON.stringify(b), {
|
|
35
|
-
status,
|
|
36
|
-
headers: { 'content-type': 'application/json' },
|
|
37
|
-
});
|
|
38
|
-
if (url.pathname === '/auth/handoff/exchange') {
|
|
39
|
-
const body: any = await req.json();
|
|
40
|
-
if (body.code === 'mhc_' + 'a'.repeat(64)) {
|
|
41
|
-
return json({
|
|
42
|
-
token: 'x.y',
|
|
43
|
-
role: 'owner',
|
|
44
|
-
project: 'stub-project',
|
|
45
|
-
// The "cell" is this same stub server.
|
|
46
|
-
url: `http://127.0.0.1:${stub!.port}`,
|
|
47
|
-
expiresAt: Date.now() + 3600_000,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
return json({ error: 'that code is not valid or has expired' }, 400);
|
|
51
|
-
}
|
|
52
|
-
if (url.pathname === '/auth/login') {
|
|
53
|
-
const body: any = await req.json();
|
|
54
|
-
return body?.token === 'x.y'
|
|
55
|
-
? json({ token: 'mau_stub_hub_token', user: { email: 'e@example.com', role: 'owner' } })
|
|
56
|
-
: json({ error: 'no' }, 401);
|
|
57
|
-
}
|
|
58
|
-
return json({ error: 'not stubbed' }, 404);
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
process.env.MAUDE_CLOUD_URL = `http://127.0.0.1:${stub.port}`;
|
|
62
|
-
process.env.MAUDE_CLOUD_CONFIG = join(scratch, 'cloud.json');
|
|
63
|
-
process.env.HUBS_CONFIG_PATH = join(scratch, 'hubs.json');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
afterAll(() => {
|
|
67
|
-
stub?.stop(true);
|
|
68
|
-
rmSync(scratch, { recursive: true, force: true });
|
|
69
|
-
delete process.env.MAUDE_CLOUD_URL;
|
|
70
|
-
delete process.env.MAUDE_CLOUD_CONFIG;
|
|
71
|
-
delete process.env.HUBS_CONFIG_PATH;
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe('parseDeepLink — untrusted input, strict shape', () => {
|
|
75
|
-
test('accepts exactly the minted shape', () => {
|
|
76
|
-
const p = parseDeepLink(
|
|
77
|
-
`maude://open/alligators?code=mhc_${'a'.repeat(64)}&origin=https://cloud.maude.sh`
|
|
78
|
-
);
|
|
79
|
-
expect(p).toEqual({ project: 'alligators', code: `mhc_${'a'.repeat(64)}` });
|
|
80
|
-
});
|
|
81
|
-
test('refuses everything else', () => {
|
|
82
|
-
for (const bad of [
|
|
83
|
-
'maude://open/alligators', // no code
|
|
84
|
-
'maude://open/Alligators?code=mhc_' + 'a'.repeat(64), // bad slug
|
|
85
|
-
'maude://open/x?code=nothex', // bad code
|
|
86
|
-
'https://evil.example/?code=mhc_' + 'a'.repeat(64), // not maude://
|
|
87
|
-
'maude://join/x/y', // join is not the open shape
|
|
88
|
-
// Over the platform's 40-char project-id cap — a drive-by link never
|
|
89
|
-
// passes through the server-side bound, so arbitrary attacker text would
|
|
90
|
-
// otherwise render in the dialog heading, prose and aria-label.
|
|
91
|
-
`maude://open/${'a'.repeat(41)}?code=mhc_${'a'.repeat(64)}`,
|
|
92
|
-
]) {
|
|
93
|
-
expect(parseDeepLink(bad)).toBeNull();
|
|
94
|
-
}
|
|
95
|
-
// …and exactly 40 is still a real project id.
|
|
96
|
-
expect(
|
|
97
|
-
parseDeepLink(`maude://open/${'a'.repeat(40)}?code=mhc_${'a'.repeat(64)}`)
|
|
98
|
-
).not.toBeNull();
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
describe('shareViewUrl — the viewer’s browser home', () => {
|
|
103
|
-
test('is the project’s OWN address — the view- gallery is gone (Phase 25 C5)', () => {
|
|
104
|
-
// `view-*` is hard-refused by tenantFromHostname, so the old derivation
|
|
105
|
-
// produced a guaranteed 404 — invisible only because window.open was a no-op
|
|
106
|
-
// in the shell. Making the opener work would have made it a dead menu item.
|
|
107
|
-
expect(shareViewUrl('https://alligators.cloud.maude.sh', 'alligators')).toBe(
|
|
108
|
-
'https://alligators.cloud.maude.sh'
|
|
109
|
-
);
|
|
110
|
-
expect(shareViewUrl('https://alligators.cloud.maude.sh', 'alligators')).not.toContain('view-');
|
|
111
|
-
});
|
|
112
|
-
test('falls back to the platform zone on garbage', () => {
|
|
113
|
-
expect(shareViewUrl('nonsense', 'p1')).toBe('https://p1.cloud.maude.sh');
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe('isDisplayableUrl — what may be rendered as a clickable address', () => {
|
|
118
|
-
const CLOUD = 'https://cloud.maude.sh';
|
|
119
|
-
|
|
120
|
-
test('the configured origin and the cloud zone’s front doors pass', () => {
|
|
121
|
-
expect(isDisplayableUrl('https://cloud.maude.sh/activate?code=ABCD', CLOUD)).toBe(true);
|
|
122
|
-
expect(isDisplayableUrl('https://alligators.cloud.maude.sh/', CLOUD)).toBe(true);
|
|
123
|
-
// A self-host origin is legitimate for its OWN addresses.
|
|
124
|
-
expect(isDisplayableUrl('http://127.0.0.1:8788/activate', 'http://127.0.0.1:8788')).toBe(true);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test('an off-zone address is never rendered — a scheme check was not enough', () => {
|
|
128
|
-
// The bug this closes: `https://evil.example/activate?code=…` from a hostile
|
|
129
|
-
// or MITM'd control plane passed a protocol-only check and still rendered as
|
|
130
|
-
// a live href with a Copy button, and middle-click never reaches the click
|
|
131
|
-
// handler, let alone the Rust zone lock.
|
|
132
|
-
for (const bad of [
|
|
133
|
-
'https://evil.example/activate?code=x',
|
|
134
|
-
'https://cloud.maude.sh.attacker.example/',
|
|
135
|
-
'javascript:alert(1)',
|
|
136
|
-
'file:///etc/passwd',
|
|
137
|
-
'not a url',
|
|
138
|
-
]) {
|
|
139
|
-
expect(isDisplayableUrl(bad, CLOUD)).toBe(false);
|
|
140
|
-
}
|
|
141
|
-
// Deep paths on a TENANT host are refused here too — same rule as the opener.
|
|
142
|
-
expect(isDisplayableUrl('https://canvas-evil.cloud.maude.sh/stored.svg', CLOUD)).toBe(false);
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe('status — names the LOCAL half so a deep link can be read', () => {
|
|
147
|
-
test('reports the open folder and its workspace, and never a credential', async () => {
|
|
148
|
-
const { createCloudEndpoints } = await import('../cloud/endpoints.ts');
|
|
149
|
-
const { mkdirSync } = await import('node:fs');
|
|
150
|
-
const repoRoot = join(scratch, 'alligators-web');
|
|
151
|
-
const designRoot = join(repoRoot, '.design');
|
|
152
|
-
mkdirSync(designRoot, { recursive: true });
|
|
153
|
-
writeFileSync(
|
|
154
|
-
join(designRoot, 'config.json'),
|
|
155
|
-
JSON.stringify({ linkedHub: { url: 'https://alligators.cloud.maude.sh', linkedAt: 1 } })
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
const r = createCloudEndpoints({ paths: { repoRoot, designRoot } }).status();
|
|
159
|
-
expect(r.status).toBe(200);
|
|
160
|
-
const json = r.json as Record<string, unknown>;
|
|
161
|
-
expect(json.project).toBe('alligators-web');
|
|
162
|
-
// No hub credential was stored in this scratch dir, so the link is reported
|
|
163
|
-
// but explicitly UNCORROBORATED — the dialog may not vouch for it.
|
|
164
|
-
expect(json.linkedHub).toEqual({
|
|
165
|
-
url: 'https://alligators.cloud.maude.sh',
|
|
166
|
-
credentialed: false,
|
|
167
|
-
});
|
|
168
|
-
// Signed out here — and the payload must stay free of token material
|
|
169
|
-
// whatever the state (this response reaches the browser surface too).
|
|
170
|
-
expect(json.connected).toBe(false);
|
|
171
|
-
expect(JSON.stringify(json)).not.toContain('token');
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test('an unlinked (or malformed-config) folder reports linkedHub: null, not an error', async () => {
|
|
175
|
-
const { createCloudEndpoints } = await import('../cloud/endpoints.ts');
|
|
176
|
-
const { mkdirSync } = await import('node:fs');
|
|
177
|
-
const repoRoot = join(scratch, 'fresh-thing');
|
|
178
|
-
const designRoot = join(repoRoot, '.design');
|
|
179
|
-
mkdirSync(designRoot, { recursive: true });
|
|
180
|
-
writeFileSync(join(designRoot, 'config.json'), '{ not json');
|
|
181
|
-
|
|
182
|
-
const json = createCloudEndpoints({ paths: { repoRoot, designRoot } }).status().json as Record<
|
|
183
|
-
string,
|
|
184
|
-
unknown
|
|
185
|
-
>;
|
|
186
|
-
expect(json.linkedHub).toBeNull();
|
|
187
|
-
expect(json.project).toBe('fresh-thing');
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
describe('isLinkedProjectRow — Connected is a state only a credentialed link earns (fix 7)', () => {
|
|
192
|
-
const CLOUD = 'https://cloud.maude.sh';
|
|
193
|
-
const row = (over = {}) =>
|
|
194
|
-
isLinkedProjectRow({
|
|
195
|
-
projectUrl: 'https://alligators.cloud.maude.sh',
|
|
196
|
-
linkedHubUrl: 'https://alligators.cloud.maude.sh',
|
|
197
|
-
linkedHubCredentialed: true,
|
|
198
|
-
cloudUrl: CLOUD,
|
|
199
|
-
...over,
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
test('linked + credentialed → the row is Connected', () => {
|
|
203
|
-
expect(row()).toBe(true);
|
|
204
|
-
});
|
|
205
|
-
test('linked but UNCREDENTIALED still offers Connect — config.json travels with the repo (B2)', () => {
|
|
206
|
-
expect(row({ linkedHubCredentialed: false })).toBe(false);
|
|
207
|
-
});
|
|
208
|
-
test('an unrelated project keeps its Connect button', () => {
|
|
209
|
-
expect(row({ projectUrl: 'https://dugmate.cloud.maude.sh' })).toBe(false);
|
|
210
|
-
});
|
|
211
|
-
test('unlinked folder: no row is Connected', () => {
|
|
212
|
-
expect(row({ linkedHubUrl: null, linkedHubCredentialed: false })).toBe(false);
|
|
213
|
-
});
|
|
214
|
-
test('self-host addresses carry no project label — exact origin decides', () => {
|
|
215
|
-
expect(
|
|
216
|
-
row({
|
|
217
|
-
projectUrl: 'http://127.0.0.1:8788',
|
|
218
|
-
linkedHubUrl: 'http://127.0.0.1:8788',
|
|
219
|
-
cloudUrl: 'http://127.0.0.1:8788',
|
|
220
|
-
})
|
|
221
|
-
).toBe(true);
|
|
222
|
-
// Two stubs on different ports are two different workspaces.
|
|
223
|
-
expect(
|
|
224
|
-
row({
|
|
225
|
-
projectUrl: 'http://127.0.0.1:8788',
|
|
226
|
-
linkedHubUrl: 'http://127.0.0.1:9999',
|
|
227
|
-
cloudUrl: 'http://127.0.0.1:8788',
|
|
228
|
-
})
|
|
229
|
-
).toBe(false);
|
|
230
|
-
});
|
|
231
|
-
test('a label on one side and none on the other never matches', () => {
|
|
232
|
-
expect(row({ projectUrl: 'http://127.0.0.1:8788' })).toBe(false);
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
describe('detach — the in-app unlink (fix 7 Disconnect)', () => {
|
|
237
|
-
test('drops linkedHub + the hub credential, cycles the runtime to solo; idempotent', async () => {
|
|
238
|
-
const { createCloudEndpoints } = await import('../cloud/endpoints.ts');
|
|
239
|
-
const { mkdirSync, existsSync } = await import('node:fs');
|
|
240
|
-
const repoRoot = join(scratch, 'detach-me');
|
|
241
|
-
const designRoot = join(repoRoot, '.design');
|
|
242
|
-
mkdirSync(designRoot, { recursive: true });
|
|
243
|
-
const hubUrl = 'https://alligators.cloud.maude.sh';
|
|
244
|
-
writeFileSync(
|
|
245
|
-
join(designRoot, 'config.json'),
|
|
246
|
-
JSON.stringify({ designRoot: '.design', linkedHub: { url: hubUrl, linkedAt: 1 } })
|
|
247
|
-
);
|
|
248
|
-
writeFileSync(
|
|
249
|
-
join(scratch, 'hubs.json'),
|
|
250
|
-
JSON.stringify({ hubs: { [hubUrl]: { token: 'mau_x', linkedAt: 1 } }, trusted: [hubUrl] })
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
const restarts: unknown[] = [];
|
|
254
|
-
const api = createCloudEndpoints({
|
|
255
|
-
paths: { repoRoot, designRoot },
|
|
256
|
-
syncControl: {
|
|
257
|
-
restart: async (linkedHub) => {
|
|
258
|
-
restarts.push(linkedHub);
|
|
259
|
-
return { syncing: false, canvases: 0, reason: 'unlinked' };
|
|
260
|
-
},
|
|
261
|
-
},
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
const r = await api.detach();
|
|
265
|
-
expect(r.status).toBe(200);
|
|
266
|
-
expect((r.json as { detached?: boolean }).detached).toBe(true);
|
|
267
|
-
// The committed half is gone, the rest of config.json survives.
|
|
268
|
-
const cfg = JSON.parse(readFileSync(join(designRoot, 'config.json'), 'utf8'));
|
|
269
|
-
expect(cfg.linkedHub).toBeUndefined();
|
|
270
|
-
expect(cfg.designRoot).toBe('.design');
|
|
271
|
-
// The credential + trust entry are gone too (the CLI unlink default).
|
|
272
|
-
const hubs = JSON.parse(readFileSync(join(scratch, 'hubs.json'), 'utf8'));
|
|
273
|
-
expect(hubs.hubs[hubUrl]).toBeUndefined();
|
|
274
|
-
expect(hubs.trusted).not.toContain(hubUrl);
|
|
275
|
-
// The runtime was cycled with the explicit unlink sentinel, not undefined.
|
|
276
|
-
expect(restarts).toEqual([null]);
|
|
277
|
-
expect(existsSync(join(designRoot, 'config.json'))).toBe(true);
|
|
278
|
-
|
|
279
|
-
// Second detach: still ok, nothing to do.
|
|
280
|
-
const again = await api.detach();
|
|
281
|
-
expect(again.status).toBe(200);
|
|
282
|
-
expect((again.json as { detached?: boolean }).detached).toBe(false);
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
describe('localIdentityHint — only an exact, corroborated agreement is silent', () => {
|
|
287
|
-
const CLOUD = 'https://cloud.maude.sh';
|
|
288
|
-
|
|
289
|
-
test('the reported case warns: one project open, the link naming another', () => {
|
|
290
|
-
// StudyFi open, "Open in Maude" pressed on alligators — one click from
|
|
291
|
-
// syncing StudyFi's designs into somebody else's workspace.
|
|
292
|
-
expect(
|
|
293
|
-
localIdentityHint({ localProject: 'AI-StudyMate', cloudUrl: CLOUD, claimed: 'alligators' })
|
|
294
|
-
).toBe('mismatch');
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
test('a name an ATTACKER chose to look like yours does not buy silence', () => {
|
|
298
|
-
// The whole point (attacker pass B1). The cloud project id is registerable by
|
|
299
|
-
// anyone, so treating "contains your folder name" as agreement handed the
|
|
300
|
-
// attacker the off-switch for the warning. And the server's 409 cannot help:
|
|
301
|
-
// the claim and the actual project agree — it IS their project.
|
|
302
|
-
for (const claimed of [
|
|
303
|
-
'alligators-design-sync',
|
|
304
|
-
'alligators-backup',
|
|
305
|
-
'shared-alligators',
|
|
306
|
-
'web-design-studio-app',
|
|
307
|
-
]) {
|
|
308
|
-
expect(localIdentityHint({ localProject: 'alligators', cloudUrl: CLOUD, claimed })).not.toBe(
|
|
309
|
-
'match'
|
|
310
|
-
);
|
|
311
|
-
expect(
|
|
312
|
-
['similar', 'mismatch'].includes(
|
|
313
|
-
localIdentityHint({ localProject: 'alligators', cloudUrl: CLOUD, claimed })
|
|
314
|
-
)
|
|
315
|
-
).toBe(true);
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
test('an exact name is the only silent name-based verdict', () => {
|
|
320
|
-
expect(
|
|
321
|
-
localIdentityHint({ localProject: 'alligators', cloudUrl: CLOUD, claimed: 'alligators' })
|
|
322
|
-
).toBe('match');
|
|
323
|
-
// Normalization still applies — casing and separators are not a difference.
|
|
324
|
-
expect(
|
|
325
|
-
localIdentityHint({ localProject: 'Alligators', cloudUrl: CLOUD, claimed: 'alligators' })
|
|
326
|
-
).toBe('match');
|
|
327
|
-
// A near-match is its own state: alike, and still NOT the same workspace.
|
|
328
|
-
expect(
|
|
329
|
-
localIdentityHint({ localProject: 'alligators-web', cloudUrl: CLOUD, claimed: 'alligators' })
|
|
330
|
-
).toBe('similar');
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
test('a CREDENTIALED link to that project reads as linked', () => {
|
|
334
|
-
expect(
|
|
335
|
-
localIdentityHint({
|
|
336
|
-
localProject: 'anything',
|
|
337
|
-
linkedHubUrl: 'https://alligators.cloud.maude.sh',
|
|
338
|
-
linkedHubCredentialed: true,
|
|
339
|
-
cloudUrl: CLOUD,
|
|
340
|
-
claimed: 'alligators',
|
|
341
|
-
})
|
|
342
|
-
).toBe('linked');
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
test('an UNCREDENTIALED linkedHub cannot vouch for anyone', () => {
|
|
346
|
-
// config.json is committed and travels with the repo, so a hostile template
|
|
347
|
-
// could otherwise make the dialog say "already linked to <attacker>" — the
|
|
348
|
-
// strongest reassurance it can give (attacker pass B2). Without a stored
|
|
349
|
-
// credential it is ignored and the folder name decides.
|
|
350
|
-
expect(
|
|
351
|
-
localIdentityHint({
|
|
352
|
-
localProject: 'my-thing',
|
|
353
|
-
linkedHubUrl: 'https://attacker.cloud.maude.sh',
|
|
354
|
-
linkedHubCredentialed: false,
|
|
355
|
-
cloudUrl: CLOUD,
|
|
356
|
-
claimed: 'attacker',
|
|
357
|
-
})
|
|
358
|
-
).toBe('mismatch');
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
test('a folder credentialed to a DIFFERENT workspace warns', () => {
|
|
362
|
-
expect(
|
|
363
|
-
localIdentityHint({
|
|
364
|
-
localProject: 'alligators',
|
|
365
|
-
linkedHubUrl: 'https://someone-else.cloud.maude.sh',
|
|
366
|
-
linkedHubCredentialed: true,
|
|
367
|
-
cloudUrl: CLOUD,
|
|
368
|
-
claimed: 'alligators',
|
|
369
|
-
})
|
|
370
|
-
).toBe('mismatch');
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
test('a short generic folder name does not buy agreement by containment', () => {
|
|
374
|
-
for (const localProject of ['web', 'app', 'src']) {
|
|
375
|
-
expect(
|
|
376
|
-
localIdentityHint({ localProject, cloudUrl: CLOUD, claimed: 'webshop-frontend' })
|
|
377
|
-
).toBe('mismatch');
|
|
378
|
-
}
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
test('an accented or over-long folder name still matches its own workspace', () => {
|
|
382
|
-
// The fold must mirror the platform's deriveProjectId (NFKD + strip + 40-cap)
|
|
383
|
-
// or the warning fires on every LEGITIMATE connect for a whole class of
|
|
384
|
-
// names — which is how a consent warning dies (attacker re-review).
|
|
385
|
-
expect(
|
|
386
|
-
localIdentityHint({ localProject: 'Zkušební tým', cloudUrl: CLOUD, claimed: 'zkusebni-tym' })
|
|
387
|
-
).toBe('match');
|
|
388
|
-
expect(
|
|
389
|
-
localIdentityHint({ localProject: 'Přátelé', cloudUrl: CLOUD, claimed: 'pratele' })
|
|
390
|
-
).toBe('match');
|
|
391
|
-
// >40 chars: the platform truncates the id, so the fold must truncate too.
|
|
392
|
-
const long = 'a-very-long-project-name-that-the-platform-truncates-at-forty';
|
|
393
|
-
expect(
|
|
394
|
-
localIdentityHint({ localProject: long, cloudUrl: CLOUD, claimed: long.slice(0, 40) })
|
|
395
|
-
).toBe('match');
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
test('no signal is "unknown", which warns — it must never read as agreement', () => {
|
|
399
|
-
// Every stub and self-hoster is http://127.0.0.1:<port>, carrying no project
|
|
400
|
-
// name. Before, that produced a reassuring neutral with a primary Connect;
|
|
401
|
-
// on the launch path an unresolved status did the same (attacker pass B3).
|
|
402
|
-
expect(
|
|
403
|
-
localIdentityHint({
|
|
404
|
-
localProject: '',
|
|
405
|
-
linkedHubUrl: 'http://127.0.0.1:4599',
|
|
406
|
-
cloudUrl: 'http://127.0.0.1:4599',
|
|
407
|
-
claimed: 'stub-project',
|
|
408
|
-
})
|
|
409
|
-
).toBe('unknown');
|
|
410
|
-
// Status not yet resolved → unknown, whatever else is on hand.
|
|
411
|
-
expect(
|
|
412
|
-
localIdentityHint({
|
|
413
|
-
localProject: 'alligators',
|
|
414
|
-
cloudUrl: CLOUD,
|
|
415
|
-
claimed: 'alligators',
|
|
416
|
-
resolved: false,
|
|
417
|
-
})
|
|
418
|
-
).toBe('unknown');
|
|
419
|
-
});
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
describe('connectOutcomeNote — what the rail says after Connect', () => {
|
|
423
|
-
test('no live payload yet: an in-flight state, not a claimed result', () => {
|
|
424
|
-
// This used to assert "Syncing with alligators — 12 canvases." and a hover
|
|
425
|
-
// pointing at the status bar. Both were wrong: the attach response reports
|
|
426
|
-
// that `runtime.start()` did not throw, which is an INTENTION, and the slot
|
|
427
|
-
// it sent people to was itself keying off `state` alone. See DDR-214 — the
|
|
428
|
-
// sentence now derives from the live payload, and this is only the fallback
|
|
429
|
-
// for the first render (and for a server too old to send one.)
|
|
430
|
-
const n = connectOutcomeNote('alligators', { syncing: true, canvases: 12 });
|
|
431
|
-
expect(n.text).toBe('Connecting to alligators — 12 canvases…');
|
|
432
|
-
expect(n.title).toContain('updates as they settle');
|
|
433
|
-
expect(n.text).not.toContain('hub sync');
|
|
434
|
-
expect(connectOutcomeNote('alligators', { syncing: true, canvases: 1 }).text).toContain(
|
|
435
|
-
'1 canvas…'
|
|
436
|
-
);
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
test('linked but nothing syncable says so — and never claims a restart would help', () => {
|
|
440
|
-
const n = connectOutcomeNote('alligators', {
|
|
441
|
-
syncing: false,
|
|
442
|
-
canvases: 0,
|
|
443
|
-
reason: 'nothing-syncable',
|
|
444
|
-
detail: 'no canvases found under .design.',
|
|
445
|
-
});
|
|
446
|
-
expect(n.text).toBe('Connected to alligators — nothing to sync yet.');
|
|
447
|
-
expect(n.title).toContain('no canvases found');
|
|
448
|
-
expect(n.text).not.toMatch(/restart/i);
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
test('any other refusal repeats the server’s own reason', () => {
|
|
452
|
-
const n = connectOutcomeNote('alligators', {
|
|
453
|
-
syncing: false,
|
|
454
|
-
canvases: 0,
|
|
455
|
-
reason: 'no-credential',
|
|
456
|
-
detail: 'No sign-in for this workspace is stored on this machine yet.',
|
|
457
|
-
});
|
|
458
|
-
expect(n.text).toContain('No sign-in for this workspace');
|
|
459
|
-
// Never the old sentence: there is no studio server a person can see.
|
|
460
|
-
expect(n.text).not.toContain('studio server');
|
|
461
|
-
});
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
describe('attachCode — the maude:// lane end to end (stubbed)', () => {
|
|
465
|
-
test('a valid code links: hub credential stored, linkedHub written token-free', async () => {
|
|
466
|
-
const { createCloudEndpoints } = await import('../cloud/endpoints.ts');
|
|
467
|
-
const designRoot = join(scratch, '.design');
|
|
468
|
-
const { mkdirSync } = await import('node:fs');
|
|
469
|
-
mkdirSync(designRoot, { recursive: true });
|
|
470
|
-
writeFileSync(join(designRoot, 'config.json'), '{}\n');
|
|
471
|
-
|
|
472
|
-
const apiEndpoints = createCloudEndpoints({ paths: { repoRoot: scratch, designRoot } });
|
|
473
|
-
const r = await apiEndpoints.attachCode(`mhc_${'a'.repeat(64)}`);
|
|
474
|
-
expect(r.status).toBe(200);
|
|
475
|
-
expect((r.json as any).ok).toBe(true);
|
|
476
|
-
expect((r.json as any).project).toBe('stub-project');
|
|
477
|
-
|
|
478
|
-
const cfg = JSON.parse(readFileSync(join(designRoot, 'config.json'), 'utf8'));
|
|
479
|
-
expect(cfg.linkedHub?.url).toContain('127.0.0.1');
|
|
480
|
-
expect(JSON.stringify(cfg)).not.toContain('mau_stub_hub_token');
|
|
481
|
-
|
|
482
|
-
// The exchange went to the CONFIGURED cloud address (this stub) — the
|
|
483
|
-
// only /auth/handoff/exchange call seen is ours.
|
|
484
|
-
expect(seen.filter((s) => s.includes('/auth/handoff/exchange')).length).toBe(1);
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
test('F2 — a link that names one project but opens another is refused, nothing linked', async () => {
|
|
488
|
-
// The person confirmed a FAMILIAR name. If the exchange opens something
|
|
489
|
-
// else, that consent was borrowed — refuse rather than point linkedHub at
|
|
490
|
-
// whoever minted the code (validate 2026-07-30, defender F2).
|
|
491
|
-
const { createCloudEndpoints } = await import('../cloud/endpoints.ts');
|
|
492
|
-
const designRoot = join(scratch, '.design-f2');
|
|
493
|
-
const { mkdirSync } = await import('node:fs');
|
|
494
|
-
mkdirSync(designRoot, { recursive: true });
|
|
495
|
-
writeFileSync(join(designRoot, 'config.json'), '{}\n');
|
|
496
|
-
|
|
497
|
-
const apiEndpoints = createCloudEndpoints({ paths: { repoRoot: scratch, designRoot } });
|
|
498
|
-
const r = await apiEndpoints.attachCode(`mhc_${'a'.repeat(64)}`, 'a-project-i-trust');
|
|
499
|
-
expect(r.status).toBe(409);
|
|
500
|
-
expect((r.json as { error: string }).error).toContain('stub-project');
|
|
501
|
-
|
|
502
|
-
const cfg = JSON.parse(readFileSync(join(designRoot, 'config.json'), 'utf8'));
|
|
503
|
-
expect(cfg.linkedHub).toBeUndefined();
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
test('F2 — a matching claim still links', async () => {
|
|
507
|
-
const { createCloudEndpoints } = await import('../cloud/endpoints.ts');
|
|
508
|
-
const designRoot = join(scratch, '.design-f2-ok');
|
|
509
|
-
const { mkdirSync } = await import('node:fs');
|
|
510
|
-
mkdirSync(designRoot, { recursive: true });
|
|
511
|
-
writeFileSync(join(designRoot, 'config.json'), '{}\n');
|
|
512
|
-
const apiEndpoints = createCloudEndpoints({ paths: { repoRoot: scratch, designRoot } });
|
|
513
|
-
const r = await apiEndpoints.attachCode(`mhc_${'a'.repeat(64)}`, 'stub-project');
|
|
514
|
-
expect(r.status).toBe(200);
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
test('linking STARTS syncing — the outcome is reported, not homework', async () => {
|
|
518
|
-
// The bug this closes: the panel used to answer a successful Connect with
|
|
519
|
-
// "restart the studio server to start syncing" — a task, naming a thing
|
|
520
|
-
// the desktop user cannot see. The attach lane now cycles the runtime and
|
|
521
|
-
// reports what happened.
|
|
522
|
-
const { createCloudEndpoints } = await import('../cloud/endpoints.ts');
|
|
523
|
-
const designRoot = join(scratch, '.design-sync');
|
|
524
|
-
const { mkdirSync } = await import('node:fs');
|
|
525
|
-
mkdirSync(designRoot, { recursive: true });
|
|
526
|
-
writeFileSync(join(designRoot, 'config.json'), '{}\n');
|
|
527
|
-
|
|
528
|
-
const handed: unknown[] = [];
|
|
529
|
-
const apiEndpoints = createCloudEndpoints({
|
|
530
|
-
paths: { repoRoot: scratch, designRoot },
|
|
531
|
-
syncControl: {
|
|
532
|
-
restart: async (linkedHub) => {
|
|
533
|
-
handed.push(linkedHub);
|
|
534
|
-
return { syncing: true, canvases: 4 };
|
|
535
|
-
},
|
|
536
|
-
},
|
|
537
|
-
});
|
|
538
|
-
const r = await apiEndpoints.attachCode(`mhc_${'a'.repeat(64)}`);
|
|
539
|
-
expect(r.status).toBe(200);
|
|
540
|
-
expect((r.json as any).sync).toEqual({ syncing: true, canvases: 4 });
|
|
541
|
-
expect((r.json as any).note).toBe('Linked — syncing 4 canvases.');
|
|
542
|
-
|
|
543
|
-
// The supervisor is handed the value BY VALUE — the same object written to
|
|
544
|
-
// config.json — so nothing that touches that committed file in between can
|
|
545
|
-
// steer the socket.
|
|
546
|
-
const cfg = JSON.parse(readFileSync(join(designRoot, 'config.json'), 'utf8'));
|
|
547
|
-
expect(handed).toEqual([cfg.linkedHub]);
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
test('a TSX opt-out survives a re-link — only the restrictive direction carries', async () => {
|
|
551
|
-
// `syncTsx: false` means "do not upload canvas bodies to this hub"
|
|
552
|
-
// (DDR-072/DDR-079). Re-linking used to overwrite linkedHub wholesale,
|
|
553
|
-
// which silently re-enabled it — harmless while nothing synced until a
|
|
554
|
-
// restart, a real disclosure now that Connect starts syncing immediately.
|
|
555
|
-
const { createCloudEndpoints } = await import('../cloud/endpoints.ts');
|
|
556
|
-
const designRoot = join(scratch, '.design-opt-out');
|
|
557
|
-
const { mkdirSync } = await import('node:fs');
|
|
558
|
-
mkdirSync(designRoot, { recursive: true });
|
|
559
|
-
writeFileSync(
|
|
560
|
-
join(designRoot, 'config.json'),
|
|
561
|
-
JSON.stringify({ linkedHub: { url: 'https://old.example', linkedAt: 1, syncTsx: false } })
|
|
562
|
-
);
|
|
563
|
-
|
|
564
|
-
const apiEndpoints = createCloudEndpoints({ paths: { repoRoot: scratch, designRoot } });
|
|
565
|
-
const r = await apiEndpoints.attachCode(`mhc_${'a'.repeat(64)}`);
|
|
566
|
-
expect(r.status).toBe(200);
|
|
567
|
-
const cfg = JSON.parse(readFileSync(join(designRoot, 'config.json'), 'utf8'));
|
|
568
|
-
expect(cfg.linkedHub.syncTsx).toBe(false);
|
|
569
|
-
expect(cfg.linkedHub.url).toContain('127.0.0.1');
|
|
570
|
-
// No supervisor wired (a plain embedder): still linked, and the note says
|
|
571
|
-
// what is left to do instead of claiming success.
|
|
572
|
-
expect((r.json as any).sync.syncing).toBe(false);
|
|
573
|
-
expect((r.json as any).note).toMatch(/Restart Maude/);
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
test('a malformed or dead code never reaches the network', async () => {
|
|
577
|
-
const { createCloudEndpoints } = await import('../cloud/endpoints.ts');
|
|
578
|
-
const apiEndpoints = createCloudEndpoints({
|
|
579
|
-
paths: { repoRoot: scratch, designRoot: join(scratch, '.design') },
|
|
580
|
-
});
|
|
581
|
-
const before = seen.length;
|
|
582
|
-
const r = await apiEndpoints.attachCode('not-a-code');
|
|
583
|
-
expect(r.status).toBe(400);
|
|
584
|
-
expect(seen.length).toBe(before);
|
|
585
|
-
|
|
586
|
-
const dead = await apiEndpoints.attachCode(`mhc_${'b'.repeat(64)}`);
|
|
587
|
-
expect(dead.status).toBe(410);
|
|
588
|
-
expect((dead.json as any).error).toMatch(/works once/);
|
|
589
|
-
});
|
|
590
|
-
});
|