@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,531 +0,0 @@
|
|
|
1
|
-
// Pulling a project into an EMPTY folder, through the real sync runtime.
|
|
2
|
-
//
|
|
3
|
-
// The unit tests around `canvas-path.ts` prove the rules; `apps/hub/test/`
|
|
4
|
-
// proves the two receivers. This proves the wiring between them on the desktop
|
|
5
|
-
// side — that the path is read from the DOCUMENT (not the listing, which cannot
|
|
6
|
-
// carry it), that the agent is built around the corrected path rather than the
|
|
7
|
-
// provisional one, that the canvas is written where the tree will find it, and
|
|
8
|
-
// that a folder with no `config.json` ends up with one describing what arrived.
|
|
9
|
-
|
|
10
|
-
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
11
|
-
import {
|
|
12
|
-
chmodSync,
|
|
13
|
-
existsSync,
|
|
14
|
-
mkdirSync,
|
|
15
|
-
mkdtempSync,
|
|
16
|
-
readFileSync,
|
|
17
|
-
rmSync,
|
|
18
|
-
symlinkSync,
|
|
19
|
-
writeFileSync,
|
|
20
|
-
} from 'node:fs';
|
|
21
|
-
import { tmpdir } from 'node:os';
|
|
22
|
-
import { join } from 'node:path';
|
|
23
|
-
import { Awareness } from 'y-protocols/awareness';
|
|
24
|
-
import * as Y from 'yjs';
|
|
25
|
-
|
|
26
|
-
import type { Context, DevServerConfig } from '../context.ts';
|
|
27
|
-
import { createBus } from '../context.ts';
|
|
28
|
-
import { createSyncRuntime, type SyncProvider } from '../sync/index.ts';
|
|
29
|
-
|
|
30
|
-
const URL_ = 'https://hub.example.com';
|
|
31
|
-
const NESTED_REL = 'ui/2026/social/summer-camp.tsx';
|
|
32
|
-
const NESTED_SLUG = 'ui-2026-social-summer-camp';
|
|
33
|
-
const BODY = 'export default () => <main>summer camp</main>;\n';
|
|
34
|
-
|
|
35
|
-
let dir: string;
|
|
36
|
-
let cfgPathEnv: string | undefined;
|
|
37
|
-
let realFetch: typeof fetch;
|
|
38
|
-
|
|
39
|
-
beforeEach(() => {
|
|
40
|
-
dir = mkdtempSync(join(tmpdir(), 'sync-path-pull-'));
|
|
41
|
-
cfgPathEnv = process.env.HUBS_CONFIG_PATH;
|
|
42
|
-
process.env.HUBS_CONFIG_PATH = join(dir, 'hubs.json');
|
|
43
|
-
writeFileSync(
|
|
44
|
-
process.env.HUBS_CONFIG_PATH,
|
|
45
|
-
JSON.stringify({ hubs: { [URL_]: { token: 'mau_test', linkedAt: 1 } } })
|
|
46
|
-
);
|
|
47
|
-
chmodSync(process.env.HUBS_CONFIG_PATH, 0o600);
|
|
48
|
-
realFetch = globalThis.fetch;
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
afterEach(() => {
|
|
52
|
-
globalThis.fetch = realFetch;
|
|
53
|
-
if (cfgPathEnv === undefined) delete process.env.HUBS_CONFIG_PATH;
|
|
54
|
-
else process.env.HUBS_CONFIG_PATH = cfgPathEnv;
|
|
55
|
-
rmSync(dir, { recursive: true, force: true });
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
/** The hub's `GET /api/documents` — names and byte counts, never a path. */
|
|
59
|
-
function hubListing(documents: Array<{ name: string; bytes: number }>): void {
|
|
60
|
-
globalThis.fetch = (async (input: RequestInfo | URL) => {
|
|
61
|
-
if (String(input).endsWith('/api/documents')) {
|
|
62
|
-
return new Response(JSON.stringify({ documents }), {
|
|
63
|
-
status: 200,
|
|
64
|
-
headers: { 'content-type': 'application/json' },
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
return new Response('nope', { status: 404 });
|
|
68
|
-
}) as typeof fetch;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function makeCtx(canvasGroups?: DevServerConfig['canvasGroups']): Context {
|
|
72
|
-
const designRoot = join(dir, 'design');
|
|
73
|
-
mkdirSync(join(designRoot, '_comments'), { recursive: true });
|
|
74
|
-
return {
|
|
75
|
-
canvasOrigin: 'http://canvas.localhost:1234',
|
|
76
|
-
cfg: {
|
|
77
|
-
name: 'test',
|
|
78
|
-
projectLabel: null,
|
|
79
|
-
designRoot: 'design',
|
|
80
|
-
canvasGroups: canvasGroups ?? [
|
|
81
|
-
{ label: 'Design system', path: 'system' },
|
|
82
|
-
{ label: 'Canvases', path: 'ui' },
|
|
83
|
-
],
|
|
84
|
-
rootClass: 'app',
|
|
85
|
-
themeDefault: 'dark',
|
|
86
|
-
tokensCssRel: 'system/colors.css',
|
|
87
|
-
teamAccentDefault: null,
|
|
88
|
-
handoffTargets: [],
|
|
89
|
-
newCanvasDir: 'ui',
|
|
90
|
-
newComponentDir: 'ui/components',
|
|
91
|
-
linkedHub: { url: URL_, linkedAt: 1 },
|
|
92
|
-
_source: 'defaults',
|
|
93
|
-
},
|
|
94
|
-
projectLabel: 'test',
|
|
95
|
-
paths: {
|
|
96
|
-
repoRoot: dir,
|
|
97
|
-
designRel: 'design',
|
|
98
|
-
designRoot,
|
|
99
|
-
serverInfoFile: join(designRoot, '_server.json'),
|
|
100
|
-
activeFile: join(designRoot, '_active.json'),
|
|
101
|
-
commentsDir: join(designRoot, '_comments'),
|
|
102
|
-
canvasStateDir: join(designRoot, '_canvas-state'),
|
|
103
|
-
historyDir: join(designRoot, '_history'),
|
|
104
|
-
tokensUrlRel: 'design/system/colors.css',
|
|
105
|
-
systemDirRel: 'system',
|
|
106
|
-
},
|
|
107
|
-
bus: createBus(),
|
|
108
|
-
} as unknown as Context;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* A provider whose document ALREADY holds what the hub holds — the body plus
|
|
113
|
-
* whatever `syncMeta.path` the peer that authored it stamped. That is the shape
|
|
114
|
-
* a real pull sees at `onceSynced`, and the reason the target cannot be decided
|
|
115
|
-
* from the listing.
|
|
116
|
-
*/
|
|
117
|
-
function hubDocProviderFactory(
|
|
118
|
-
seed: Record<string, { body: string; path?: string; css?: string; movedTo?: string }>,
|
|
119
|
-
docs?: Map<string, Y.Doc>
|
|
120
|
-
) {
|
|
121
|
-
return (args: { documentName: string; document?: Y.Doc }): SyncProvider => {
|
|
122
|
-
const doc = args.document ?? new Y.Doc();
|
|
123
|
-
const slug = args.documentName.split('/').pop() as string;
|
|
124
|
-
const entry = seed[slug];
|
|
125
|
-
if (entry) {
|
|
126
|
-
doc.transact(() => {
|
|
127
|
-
doc.getText('html').insert(0, entry.body);
|
|
128
|
-
if (entry.path) doc.getMap('syncMeta').set('path', entry.path);
|
|
129
|
-
if (entry.movedTo) doc.getMap('syncMeta').set('movedTo', entry.movedTo);
|
|
130
|
-
if (entry.css) doc.getText('css').insert(0, entry.css);
|
|
131
|
-
}, 'hub');
|
|
132
|
-
}
|
|
133
|
-
docs?.set(slug, doc);
|
|
134
|
-
return {
|
|
135
|
-
document: doc,
|
|
136
|
-
awareness: new Awareness(doc),
|
|
137
|
-
async onceSynced() {},
|
|
138
|
-
destroy() {},
|
|
139
|
-
};
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
describe('a peer with an empty design root pulls the project down whole', () => {
|
|
144
|
-
test('the canvas lands at the path its author gave it', async () => {
|
|
145
|
-
hubListing([{ name: `ws/acme/main/${NESTED_SLUG}`, bytes: BODY.length }]);
|
|
146
|
-
const ctx = makeCtx();
|
|
147
|
-
const runtime = createSyncRuntime(ctx, {
|
|
148
|
-
providerFactory: hubDocProviderFactory({
|
|
149
|
-
[NESTED_SLUG]: { body: BODY, path: NESTED_REL },
|
|
150
|
-
}),
|
|
151
|
-
});
|
|
152
|
-
await runtime?.start();
|
|
153
|
-
await runtime?.stop();
|
|
154
|
-
|
|
155
|
-
const wanted = join(ctx.paths.designRoot, NESTED_REL);
|
|
156
|
-
expect(existsSync(wanted)).toBe(true);
|
|
157
|
-
expect(readFileSync(wanted, 'utf8')).toBe(BODY);
|
|
158
|
-
// And NOT flat at the design root, where nothing would list it.
|
|
159
|
-
expect(existsSync(join(ctx.paths.designRoot, `${NESTED_SLUG}.tsx`))).toBe(false);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test('a document with NO path still arrives, inside a canvas group', async () => {
|
|
163
|
-
hubListing([{ name: 'ws/acme/main/ui-legacy', bytes: 10 }]);
|
|
164
|
-
const ctx = makeCtx();
|
|
165
|
-
const runtime = createSyncRuntime(ctx, {
|
|
166
|
-
providerFactory: hubDocProviderFactory({
|
|
167
|
-
'ui-legacy': { body: 'export default () => null;\n' },
|
|
168
|
-
}),
|
|
169
|
-
});
|
|
170
|
-
await runtime?.start();
|
|
171
|
-
await runtime?.stop();
|
|
172
|
-
|
|
173
|
-
// `ui/legacy.tsx`, not `ui/ui-legacy.tsx` — the latter would be visible and
|
|
174
|
-
// would slug to a DIFFERENT document.
|
|
175
|
-
expect(existsSync(join(ctx.paths.designRoot, 'ui', 'legacy.tsx'))).toBe(true);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test('a hostile path is refused and the canvas still arrives', async () => {
|
|
179
|
-
hubListing([{ name: 'ws/acme/main/ui-legacy', bytes: 10 }]);
|
|
180
|
-
const ctx = makeCtx();
|
|
181
|
-
const runtime = createSyncRuntime(ctx, {
|
|
182
|
-
providerFactory: hubDocProviderFactory({
|
|
183
|
-
'ui-legacy': { body: 'x\n', path: '../../../../tmp/pwned.tsx' },
|
|
184
|
-
}),
|
|
185
|
-
});
|
|
186
|
-
await runtime?.start();
|
|
187
|
-
await runtime?.stop();
|
|
188
|
-
|
|
189
|
-
expect(existsSync(join(ctx.paths.designRoot, 'ui', 'legacy.tsx'))).toBe(true);
|
|
190
|
-
expect(existsSync(join(dir, '..', 'tmp', 'pwned.tsx'))).toBe(false);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
test('a fresh link accepts the author’s OWN group, and writes it down', async () => {
|
|
194
|
-
// `config.json` is not part of the sync lane, so a bare folder runs on the
|
|
195
|
-
// DEFAULT groups. Without the fresh-link relaxation a project whose author
|
|
196
|
-
// calls their group `screens` would have every path refused and arrive
|
|
197
|
-
// invisible — the empty-folder case, in the shape it actually takes.
|
|
198
|
-
hubListing([{ name: 'ws/acme/main/screens-home', bytes: 10 }]);
|
|
199
|
-
const ctx = makeCtx();
|
|
200
|
-
const runtime = createSyncRuntime(ctx, {
|
|
201
|
-
providerFactory: hubDocProviderFactory({
|
|
202
|
-
'screens-home': { body: 'x\n', path: 'screens/home.tsx' },
|
|
203
|
-
}),
|
|
204
|
-
});
|
|
205
|
-
await runtime?.start();
|
|
206
|
-
await runtime?.stop();
|
|
207
|
-
|
|
208
|
-
expect(existsSync(join(ctx.paths.designRoot, 'screens', 'home.tsx'))).toBe(true);
|
|
209
|
-
// …and the project now declares itself, so the next boot needs no relaxation.
|
|
210
|
-
const cfg = JSON.parse(readFileSync(join(ctx.paths.designRoot, 'config.json'), 'utf8'));
|
|
211
|
-
expect(cfg.canvasGroups.map((g: { path: string }) => g.path)).toContain('screens');
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
test('a project that already declared itself is NEVER overwritten', async () => {
|
|
215
|
-
const ctx = makeCtx();
|
|
216
|
-
mkdirSync(join(ctx.paths.designRoot, 'ui'), { recursive: true });
|
|
217
|
-
const cfgFile = join(ctx.paths.designRoot, 'config.json');
|
|
218
|
-
const mine = `${JSON.stringify({ name: 'mine', canvasGroups: [{ label: 'UI', path: 'ui' }] })}\n`;
|
|
219
|
-
writeFileSync(cfgFile, mine);
|
|
220
|
-
hubListing([{ name: 'ws/acme/main/screens-home', bytes: 10 }]);
|
|
221
|
-
|
|
222
|
-
const runtime = createSyncRuntime(ctx, {
|
|
223
|
-
providerFactory: hubDocProviderFactory({
|
|
224
|
-
'screens-home': { body: 'x\n', path: 'screens/home.tsx' },
|
|
225
|
-
}),
|
|
226
|
-
});
|
|
227
|
-
await runtime?.start();
|
|
228
|
-
await runtime?.stop();
|
|
229
|
-
|
|
230
|
-
expect(readFileSync(cfgFile, 'utf8')).toBe(mine);
|
|
231
|
-
// The undeclared group is refused, so the canvas falls back — still arriving.
|
|
232
|
-
expect(existsSync(join(ctx.paths.designRoot, 'screens', 'home.tsx'))).toBe(false);
|
|
233
|
-
expect(existsSync(join(ctx.paths.designRoot, 'screens-home.tsx'))).toBe(true);
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
describe('a refused path ends the pull — it does not redirect it', () => {
|
|
238
|
-
// `canvasSlugFromRel` is lossy: `ui/Desk A.tsx` flattens to `ui-desk_a`, and
|
|
239
|
-
// the provisional target derived back from that slug is `ui/desk_a.tsx` — a
|
|
240
|
-
// DIFFERENT file. So when the document's own path was refused (here: the file
|
|
241
|
-
// is already on this disk, which is what `admitPullTarget` protects), falling
|
|
242
|
-
// through left the descriptor on the provisional target and the reconcile
|
|
243
|
-
// wrote the canvas there. The peer then held the canvas twice, both copies
|
|
244
|
-
// flattening to one slug — a collision, which takes the canvas out of sync on
|
|
245
|
-
// that machine entirely. Observed on a live cell for every canvas whose name
|
|
246
|
-
// contains a space.
|
|
247
|
-
test('a document whose path already exists locally writes no slug-shaped ghost', async () => {
|
|
248
|
-
const SPACED_REL = 'ui/Desk A.tsx';
|
|
249
|
-
const SPACED_SLUG = 'ui-desk_a';
|
|
250
|
-
const LOCAL = 'export default () => <main>the copy already here</main>;\n';
|
|
251
|
-
hubListing([{ name: `ws/acme/main/${SPACED_SLUG}`, bytes: BODY.length }]);
|
|
252
|
-
const ctx = makeCtx();
|
|
253
|
-
mkdirSync(join(ctx.paths.designRoot, 'ui'), { recursive: true });
|
|
254
|
-
// Present, but out of the sync set — a `syncable: false` sidecar, the TSX
|
|
255
|
-
// sandbox gate, or (on a cell) a file the hub's own workspace agent wrote.
|
|
256
|
-
writeFileSync(join(ctx.paths.designRoot, SPACED_REL), LOCAL);
|
|
257
|
-
writeFileSync(
|
|
258
|
-
join(ctx.paths.designRoot, 'ui', 'Desk A.meta.json'),
|
|
259
|
-
JSON.stringify({ syncable: false })
|
|
260
|
-
);
|
|
261
|
-
const runtime = createSyncRuntime(ctx, {
|
|
262
|
-
providerFactory: hubDocProviderFactory({
|
|
263
|
-
[SPACED_SLUG]: { body: BODY, path: SPACED_REL },
|
|
264
|
-
}),
|
|
265
|
-
});
|
|
266
|
-
await runtime?.start();
|
|
267
|
-
await runtime?.stop();
|
|
268
|
-
|
|
269
|
-
expect(existsSync(join(ctx.paths.designRoot, 'ui', 'desk_a.tsx'))).toBe(false);
|
|
270
|
-
// And the file that WAS there is kept, byte for byte.
|
|
271
|
-
expect(readFileSync(join(ctx.paths.designRoot, SPACED_REL), 'utf8')).toBe(LOCAL);
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
describe('a PULLED document may not talk itself out of being retired', () => {
|
|
276
|
-
// THE FINDING (attacker seat, 2026-08-19, HIGH). The self-stamp repair exists
|
|
277
|
-
// for the machine that performed a move, whose canvas descriptor came from its
|
|
278
|
-
// own disk scan. A PULLED canvas has no such descriptor: its path is the
|
|
279
|
-
// provisional one derived from the document NAME — which the hub chose. Since
|
|
280
|
-
// the hub also writes `movedTo`, it would control both sides of the "it moved
|
|
281
|
-
// to where it already is" comparison, and a repair would become a write to a
|
|
282
|
-
// path `resolvePulledTarget` and `admitPullTarget` never approved. That is the
|
|
283
|
-
// resurrection primitive the retirement release exists to deny.
|
|
284
|
-
test('a hub-named document whose movedTo equals its own provisional path is NOT written', async () => {
|
|
285
|
-
const SLUG = 'ui-evil';
|
|
286
|
-
hubListing([{ name: SLUG, bytes: BODY.length }]);
|
|
287
|
-
const ctx = makeCtx();
|
|
288
|
-
const runtime = createSyncRuntime(ctx, {
|
|
289
|
-
providerFactory: hubDocProviderFactory({
|
|
290
|
-
// Both halves chosen by the hub: the name fixes the provisional target
|
|
291
|
-
// `ui/evil.tsx`, and the stamp claims the canvas moved exactly there.
|
|
292
|
-
[SLUG]: { body: BODY, movedTo: 'ui/evil.tsx' },
|
|
293
|
-
}),
|
|
294
|
-
});
|
|
295
|
-
await runtime?.start();
|
|
296
|
-
await runtime?.stop();
|
|
297
|
-
|
|
298
|
-
expect(existsSync(join(ctx.paths.designRoot, 'ui', 'evil.tsx'))).toBe(false);
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
describe('boot runs the flat-fallback quarantine BEFORE the scan (fix 4)', () => {
|
|
303
|
-
test('a pre-fix-5 flat twin is in _trash by the time the runtime settles', async () => {
|
|
304
|
-
const ctx = makeCtx();
|
|
305
|
-
mkdirSync(join(ctx.paths.designRoot, 'ui'), { recursive: true });
|
|
306
|
-
writeFileSync(join(ctx.paths.designRoot, 'ui/welcome.tsx'), 'grouped\n');
|
|
307
|
-
writeFileSync(join(ctx.paths.designRoot, 'ui-welcome.tsx'), 'flat stub\n');
|
|
308
|
-
hubListing([]);
|
|
309
|
-
|
|
310
|
-
const runtime = createSyncRuntime(ctx, { providerFactory: hubDocProviderFactory({}) });
|
|
311
|
-
await runtime?.start();
|
|
312
|
-
await runtime?.stop();
|
|
313
|
-
|
|
314
|
-
expect(existsSync(join(ctx.paths.designRoot, 'ui-welcome.tsx'))).toBe(false);
|
|
315
|
-
expect(existsSync(join(ctx.paths.designRoot, 'ui/welcome.tsx'))).toBe(true);
|
|
316
|
-
expect(existsSync(join(ctx.paths.designRoot, '_trash'))).toBe(true);
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
describe('the stamp race — the path is on the doc BEFORE the handshake (fix 5)', () => {
|
|
321
|
-
test('a local canvas stamps syncMeta.path before onceSynced, so the hub’s first store sees it', async () => {
|
|
322
|
-
// The RCA bug: the stamp lived only in the post-reconcile tail, so the
|
|
323
|
-
// hub's FIRST onDocumentStored saw a bare body, memoised the flat fallback
|
|
324
|
-
// in its pathIndex, and the real nested path could never win — a stub was
|
|
325
|
-
// born whose dynamic import failed forever.
|
|
326
|
-
const ctx = makeCtx();
|
|
327
|
-
const bodyAbs = join(ctx.paths.designRoot, NESTED_REL);
|
|
328
|
-
mkdirSync(join(ctx.paths.designRoot, 'ui/2026/social'), { recursive: true });
|
|
329
|
-
writeFileSync(bodyAbs, BODY);
|
|
330
|
-
hubListing([]);
|
|
331
|
-
|
|
332
|
-
let pathAtHandshake: unknown = 'never-called';
|
|
333
|
-
const runtime = createSyncRuntime(ctx, {
|
|
334
|
-
canvases: [
|
|
335
|
-
{
|
|
336
|
-
slug: NESTED_SLUG,
|
|
337
|
-
html: bodyAbs,
|
|
338
|
-
comments: join(ctx.paths.commentsDir, `${NESTED_SLUG}.json`),
|
|
339
|
-
annotations: join(ctx.paths.designRoot, `${NESTED_SLUG}.annotations.svg`),
|
|
340
|
-
meta: bodyAbs.replace(/\.tsx$/, '.meta.json'),
|
|
341
|
-
css: bodyAbs.replace(/\.tsx$/, '.css'),
|
|
342
|
-
},
|
|
343
|
-
],
|
|
344
|
-
providerFactory: (args) => {
|
|
345
|
-
const doc = args.document ?? new Y.Doc();
|
|
346
|
-
return {
|
|
347
|
-
document: doc,
|
|
348
|
-
awareness: new Awareness(doc),
|
|
349
|
-
async onceSynced() {
|
|
350
|
-
// What the hub can see by the time the handshake settles — the
|
|
351
|
-
// stamp must ALREADY be there, not arrive with the reconcile tail.
|
|
352
|
-
pathAtHandshake = doc.getMap('syncMeta').get('path');
|
|
353
|
-
},
|
|
354
|
-
destroy() {},
|
|
355
|
-
};
|
|
356
|
-
},
|
|
357
|
-
});
|
|
358
|
-
await runtime?.start();
|
|
359
|
-
await runtime?.stop();
|
|
360
|
-
|
|
361
|
-
expect(pathAtHandshake).toBe(NESTED_REL);
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
describe("rule 7 governs a path's IDENTITY, not what already lives there", () => {
|
|
366
|
-
test('a pulled canvas never lands on a file that already exists locally', async () => {
|
|
367
|
-
// `scanCanvases` omits a canvas whose `.meta.json` says `syncable: false` —
|
|
368
|
-
// a security opt-out a hub must not be able to flip. Omitted means absent
|
|
369
|
-
// from `localCanvases`, which means the hub's document for that slug is
|
|
370
|
-
// classified hub-ONLY and pulled. Before the path lane that was benign: the
|
|
371
|
-
// body landed flat at the design root, loaded by nothing. Honouring a remote
|
|
372
|
-
// path would land it on the real file.
|
|
373
|
-
const ctx = makeCtx();
|
|
374
|
-
mkdirSync(join(ctx.paths.designRoot, 'ui'), { recursive: true });
|
|
375
|
-
const guarded = join(ctx.paths.designRoot, 'ui', 'Card.tsx');
|
|
376
|
-
writeFileSync(guarded, 'export default () => <b>MINE</b>;\n');
|
|
377
|
-
writeFileSync(
|
|
378
|
-
join(ctx.paths.designRoot, 'ui', 'Card.meta.json'),
|
|
379
|
-
JSON.stringify({ syncable: false })
|
|
380
|
-
);
|
|
381
|
-
hubListing([{ name: 'ws/acme/main/ui-card', bytes: 10 }]);
|
|
382
|
-
|
|
383
|
-
const docs = new Map<string, Y.Doc>();
|
|
384
|
-
const runtime = createSyncRuntime(ctx, {
|
|
385
|
-
providerFactory: hubDocProviderFactory(
|
|
386
|
-
{ 'ui-card': { body: 'export default () => <b>HUB</b>;\n', path: 'ui/Card.tsx' } },
|
|
387
|
-
docs
|
|
388
|
-
),
|
|
389
|
-
});
|
|
390
|
-
await runtime?.start();
|
|
391
|
-
|
|
392
|
-
// The boot moment is not the whole story — the provider now OWNS a path, so
|
|
393
|
-
// every later hub edit flows to it. Push one, the way a hub actually would.
|
|
394
|
-
const doc = docs.get('ui-card');
|
|
395
|
-
if (doc) {
|
|
396
|
-
const text = doc.getText('html');
|
|
397
|
-
doc.transact(() => {
|
|
398
|
-
text.delete(0, text.length);
|
|
399
|
-
text.insert(0, 'export default () => <b>PWNED</b>;\n');
|
|
400
|
-
}, 'hub');
|
|
401
|
-
}
|
|
402
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
403
|
-
await runtime?.stop();
|
|
404
|
-
|
|
405
|
-
expect(readFileSync(guarded, 'utf8')).toContain('MINE');
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
test('a pulled canvas cannot overwrite the served token stylesheet', async () => {
|
|
409
|
-
// `system` is a DEFAULT canvas group and the `.css` sibling is derived from
|
|
410
|
-
// the body path, so `system/colors_and_type.tsx` satisfies every rule and
|
|
411
|
-
// writes its css lane exactly where `tokensCssRel` is served from.
|
|
412
|
-
const ctx = makeCtx();
|
|
413
|
-
mkdirSync(join(ctx.paths.designRoot, 'system'), { recursive: true });
|
|
414
|
-
const tokens = join(ctx.paths.designRoot, 'system', 'colors.css');
|
|
415
|
-
writeFileSync(tokens, ':root{--bg-0:#fff}\n');
|
|
416
|
-
hubListing([{ name: 'ws/acme/main/system-colors', bytes: 10 }]);
|
|
417
|
-
|
|
418
|
-
const runtime = createSyncRuntime(ctx, {
|
|
419
|
-
providerFactory: hubDocProviderFactory({
|
|
420
|
-
'system-colors': {
|
|
421
|
-
body: 'x\n',
|
|
422
|
-
path: 'system/colors.tsx',
|
|
423
|
-
// The lane that does the damage — it is written to the body path's
|
|
424
|
-
// `.css` sibling, which here IS the served `tokensCssRel`.
|
|
425
|
-
css: ':root{--bg-0:url("https://evil.example/?x")}\n',
|
|
426
|
-
},
|
|
427
|
-
}),
|
|
428
|
-
});
|
|
429
|
-
await runtime?.start();
|
|
430
|
-
await runtime?.stop();
|
|
431
|
-
|
|
432
|
-
expect(readFileSync(tokens, 'utf8')).toBe(':root{--bg-0:#fff}\n');
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
test('containment is checked THROUGH symlinks, which resolve() cannot see', async () => {
|
|
436
|
-
const ctx = makeCtx();
|
|
437
|
-
const outside = mkdtempSync(join(tmpdir(), 'sync-path-outside-'));
|
|
438
|
-
mkdirSync(join(ctx.paths.designRoot, 'ui'), { recursive: true });
|
|
439
|
-
symlinkSync(outside, join(ctx.paths.designRoot, 'ui', 'escape'), 'dir');
|
|
440
|
-
hubListing([{ name: 'ws/acme/main/ui-escape-pwned', bytes: 10 }]);
|
|
441
|
-
|
|
442
|
-
const runtime = createSyncRuntime(ctx, {
|
|
443
|
-
providerFactory: hubDocProviderFactory({
|
|
444
|
-
'ui-escape-pwned': { body: 'x\n', path: 'ui/escape/pwned.tsx' },
|
|
445
|
-
}),
|
|
446
|
-
});
|
|
447
|
-
await runtime?.start();
|
|
448
|
-
await runtime?.stop();
|
|
449
|
-
|
|
450
|
-
expect(existsSync(join(outside, 'pwned.tsx'))).toBe(false);
|
|
451
|
-
rmSync(outside, { recursive: true, force: true });
|
|
452
|
-
});
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
describe('the untrusted markers name the path the body actually lands at', () => {
|
|
456
|
-
test('a nested pulled canvas is marked at its REAL path, not the provisional one', async () => {
|
|
457
|
-
// The markers are computed from the descriptor set BEFORE any document has
|
|
458
|
-
// synced — so every pulled entry is still the fallback — and the descriptors
|
|
459
|
-
// are then mutated in place by `relocatePulled`. Marking once left
|
|
460
|
-
// `_untrusted/INDEX.json` and the `.claudeignore` block naming a file that
|
|
461
|
-
// is never created, while the genuinely hub-pushed body sat elsewhere,
|
|
462
|
-
// listed nowhere. That is the DDR-054 §3 F3 control pointing at a phantom.
|
|
463
|
-
hubListing([{ name: `ws/acme/main/${NESTED_SLUG}`, bytes: BODY.length }]);
|
|
464
|
-
const ctx = makeCtx();
|
|
465
|
-
const runtime = createSyncRuntime(ctx, {
|
|
466
|
-
providerFactory: hubDocProviderFactory({
|
|
467
|
-
[NESTED_SLUG]: { body: BODY, path: NESTED_REL },
|
|
468
|
-
}),
|
|
469
|
-
});
|
|
470
|
-
await runtime?.start();
|
|
471
|
-
await runtime?.stop();
|
|
472
|
-
|
|
473
|
-
const index = readFileSync(join(ctx.paths.designRoot, '_untrusted', 'INDEX.json'), 'utf8');
|
|
474
|
-
expect(index).toContain('ui/2026/social/summer-camp.tsx');
|
|
475
|
-
// The provisional fallback was never created, so naming it is naming nothing.
|
|
476
|
-
expect(index).not.toContain('ui/2026-social-summer-camp.tsx');
|
|
477
|
-
});
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
describe('the fresh-link relaxation closes behind itself', () => {
|
|
481
|
-
test('only ONE undeclared group is ever learned', async () => {
|
|
482
|
-
// `freshLink` was a const, so after the first config was written every
|
|
483
|
-
// FURTHER undeclared group was accepted and appended — the relaxation
|
|
484
|
-
// perpetuating itself, and a hub free to plant directories all session.
|
|
485
|
-
hubListing([
|
|
486
|
-
{ name: 'ws/acme/main/screens-a', bytes: 10 },
|
|
487
|
-
{ name: 'ws/acme/main/vendor-b', bytes: 10 },
|
|
488
|
-
]);
|
|
489
|
-
const ctx = makeCtx();
|
|
490
|
-
const runtime = createSyncRuntime(ctx, {
|
|
491
|
-
providerFactory: hubDocProviderFactory({
|
|
492
|
-
'screens-a': { body: 'x\n', path: 'screens/a.tsx' },
|
|
493
|
-
'vendor-b': { body: 'x\n', path: 'vendor/b.tsx' },
|
|
494
|
-
}),
|
|
495
|
-
});
|
|
496
|
-
await runtime?.start();
|
|
497
|
-
await runtime?.stop();
|
|
498
|
-
|
|
499
|
-
const cfg = JSON.parse(readFileSync(join(ctx.paths.designRoot, 'config.json'), 'utf8'));
|
|
500
|
-
const learned = cfg.canvasGroups
|
|
501
|
-
.map((g: { path: string }) => g.path)
|
|
502
|
-
.filter((p: string) => p !== 'system' && p !== 'ui');
|
|
503
|
-
expect(learned.length).toBe(1);
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
test('a design root with work in it is NOT a fresh link', async () => {
|
|
507
|
-
// `localCanvases.length === 0` is a fact about the SCAN, which walks only
|
|
508
|
-
// declared groups and applies the syncable + sandbox gates — so a project
|
|
509
|
-
// with real work scans to zero for several innocent reasons, and a hub must
|
|
510
|
-
// not get to author a config.json into it.
|
|
511
|
-
const ctx = makeCtx();
|
|
512
|
-
mkdirSync(join(ctx.paths.designRoot, 'ui'), { recursive: true });
|
|
513
|
-
writeFileSync(join(ctx.paths.designRoot, 'ui', 'Existing.tsx'), 'x\n');
|
|
514
|
-
writeFileSync(
|
|
515
|
-
join(ctx.paths.designRoot, 'ui', 'Existing.meta.json'),
|
|
516
|
-
JSON.stringify({ syncable: false })
|
|
517
|
-
);
|
|
518
|
-
hubListing([{ name: 'ws/acme/main/screens-a', bytes: 10 }]);
|
|
519
|
-
|
|
520
|
-
const runtime = createSyncRuntime(ctx, {
|
|
521
|
-
providerFactory: hubDocProviderFactory({
|
|
522
|
-
'screens-a': { body: 'x\n', path: 'screens/a.tsx' },
|
|
523
|
-
}),
|
|
524
|
-
});
|
|
525
|
-
await runtime?.start();
|
|
526
|
-
await runtime?.stop();
|
|
527
|
-
|
|
528
|
-
expect(existsSync(join(ctx.paths.designRoot, 'config.json'))).toBe(false);
|
|
529
|
-
expect(existsSync(join(ctx.paths.designRoot, 'screens', 'a.tsx'))).toBe(false);
|
|
530
|
-
});
|
|
531
|
-
});
|