@1agh/maude 1.0.11 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/studio/acp/bridge.ts +18 -13
- package/apps/studio/acp/transcript-io.ts +394 -0
- package/apps/studio/acp/transcript.ts +103 -38
- package/apps/studio/api.ts +139 -26
- package/apps/studio/bin/_import-figma.mjs +57 -11
- package/apps/studio/bin/_seed-fixture.mjs +163 -0
- package/apps/studio/canvas-imports.ts +89 -0
- package/apps/studio/client/app.jsx +257 -7
- package/apps/studio/client/export-center.jsx +15 -7
- package/apps/studio/client/panels/SyncPanel.jsx +125 -4
- package/apps/studio/client/report-bug.jsx +19 -1
- package/apps/studio/client/styles/3-shell-maude.css +34 -0
- package/apps/studio/client/styles/4-components.css +26 -0
- package/apps/studio/collab/persistence.ts +132 -16
- package/apps/studio/collab/registry.ts +10 -18
- package/apps/studio/debug-bundle.ts +13 -0
- package/apps/studio/dist/client.bundle.js +1128 -1128
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/export-dialog.tsx +54 -0
- package/apps/studio/exporters/degraded.ts +19 -3
- package/apps/studio/exporters/ghostscript.ts +194 -0
- package/apps/studio/exporters/pdf-fonts.ts +261 -0
- package/apps/studio/exporters/pdf.ts +152 -3
- package/apps/studio/exporters/remote.ts +36 -1
- package/apps/studio/fs-watch.ts +45 -1
- package/apps/studio/http.ts +7 -1
- package/apps/studio/mem.ts +45 -9
- package/apps/studio/server.ts +11 -4
- package/apps/studio/sync/agent.ts +136 -29
- package/apps/studio/sync/asset-push.ts +1 -47
- package/apps/studio/sync/codec.ts +122 -31
- package/apps/studio/sync/cold-start-apply.ts +7 -0
- package/apps/studio/sync/cold-start.ts +118 -22
- package/apps/studio/sync/comment-identity.ts +68 -0
- package/apps/studio/sync/connection-state.ts +15 -1
- package/apps/studio/sync/file-ledger.ts +117 -0
- package/apps/studio/sync/file-plane.ts +623 -27
- package/apps/studio/sync/index.ts +711 -20
- package/apps/studio/sync/journal-client.ts +13 -1
- package/apps/studio/sync/migrate-seed.ts +40 -3
- package/apps/studio/sync/presentation.ts +26 -3
- package/apps/studio/sync/retry-after.ts +169 -0
- package/apps/studio/sync/seed-progress.ts +218 -0
- package/apps/studio/sync/status.ts +122 -5
- package/apps/studio/sync/transport-error.ts +111 -0
- package/apps/studio/whats-new.json +72 -0
- package/cli/bin/maude.mjs +2 -0
- package/cli/commands/codex.mjs +191 -0
- package/cli/commands/harness.mjs +1051 -0
- package/cli/commands/help.mjs +13 -0
- package/cli/lib/design-link.mjs +190 -2
- package/cli/lib/harness/capabilities.mjs +302 -0
- package/cli/lib/harness/codex-runtime.mjs +889 -0
- package/cli/lib/harness/compatibility.mjs +27 -0
- package/cli/lib/harness/discover-claude.mjs +1393 -0
- package/cli/lib/harness/managed-state.mjs +1296 -0
- package/cli/lib/harness/model.mjs +86 -0
- package/cli/lib/harness/secrets.mjs +390 -0
- package/cli/lib/harness/targets/codex.mjs +1141 -0
- package/cli/lib/harness/targets/opencode.mjs +807 -0
- package/cli/lib/harness/transaction.mjs +1099 -0
- package/cli/lib/hubs-config.mjs +35 -8
- package/cli/templates/harness/opencode/maude-projector.ts +58 -0
- package/docs/harness-capability-matrix.md +125 -0
- package/docs/harness-environment-projection.md +95 -0
- package/package.json +32 -9
- package/plugins/flow/.claude-plugin/config.schema.json +8 -0
- package/apps/studio/.ai/cache/_stats.json +0 -7
- package/apps/studio/bin/_agent-browser-safe.test.mjs +0 -165
- package/apps/studio/bin/_curl-local.test.mjs +0 -280
- package/apps/studio/bin/_pw-launch.test.mjs +0 -90
- package/apps/studio/bin/_smart-frames.test.mjs +0 -195
- package/apps/studio/bin/_transcribe.test.mjs +0 -80
- package/apps/studio/bun.lock +0 -928
- package/apps/studio/draw/test/animate.test.ts +0 -172
- package/apps/studio/draw/test/brush.test.ts +0 -213
- package/apps/studio/draw/test/composition.test.ts +0 -141
- package/apps/studio/draw/test/geometry.test.ts +0 -199
- package/apps/studio/draw/test/gradient.test.ts +0 -167
- package/apps/studio/draw/test/layout.test.ts +0 -120
- package/apps/studio/draw/test/morph.test.ts +0 -107
- package/apps/studio/draw/test/optimize.test.ts +0 -40
- package/apps/studio/draw/test/palette.test.ts +0 -123
- package/apps/studio/draw/test/primitives.test.ts +0 -129
- package/apps/studio/draw/test/serialize-animate.test.ts +0 -142
- package/apps/studio/draw/test/serialize.test.ts +0 -105
- package/apps/studio/figma/assets.test.ts +0 -464
- package/apps/studio/figma/client.test.ts +0 -395
- package/apps/studio/figma/codegen-client.test.ts +0 -276
- package/apps/studio/figma/codegen-fonts.test.ts +0 -103
- package/apps/studio/figma/codegen-values.test.ts +0 -179
- package/apps/studio/figma/comments-to-strokes.test.ts +0 -194
- package/apps/studio/figma/fig-decode.test.ts +0 -788
- package/apps/studio/figma/fig-differential.test.ts +0 -182
- package/apps/studio/figma/fig-translator.test.ts +0 -192
- package/apps/studio/figma/fig-vector.test.ts +0 -113
- package/apps/studio/figma/from-codegen.test.ts +0 -408
- package/apps/studio/figma/sanitize.test.ts +0 -325
- package/apps/studio/figma/tailwind-map.test.ts +0 -142
- package/apps/studio/figma/to-artboard.test.ts +0 -808
- package/apps/studio/figma/to-render.test.ts +0 -180
- package/apps/studio/figma/to-strokes-roundtrip.test.ts +0 -152
- package/apps/studio/figma/to-strokes.test.ts +0 -705
- package/apps/studio/figma/to-tokens.test.ts +0 -321
- package/apps/studio/figma/url.test.ts +0 -167
- package/apps/studio/footage/schema.test.ts +0 -287
- package/apps/studio/generation/adapters/groq.test.ts +0 -80
- package/apps/studio/generation/audio-library.test.ts +0 -95
- package/apps/studio/generation/captions.test.ts +0 -78
- package/apps/studio/generation/elevenlabs.test.ts +0 -223
- package/apps/studio/generation/gemini.test.ts +0 -350
- package/apps/studio/generation/jobs.test.ts +0 -128
- package/apps/studio/generation/keys.test.ts +0 -75
- package/apps/studio/generation/prefs.test.ts +0 -95
- package/apps/studio/generation/types.test.ts +0 -123
- package/apps/studio/generation/whisper-models.test.ts +0 -179
- package/apps/studio/test/_helpers.ts +0 -108
- package/apps/studio/test/acp-activity-endpoint.test.ts +0 -183
- package/apps/studio/test/acp-activity.test.ts +0 -154
- package/apps/studio/test/acp-ai-activity.test.ts +0 -182
- package/apps/studio/test/acp-attachment-serve.test.ts +0 -74
- package/apps/studio/test/acp-bootstrap-brief.test.ts +0 -178
- package/apps/studio/test/acp-branch-guard.test.ts +0 -123
- package/apps/studio/test/acp-bridge-lifetime.test.ts +0 -475
- package/apps/studio/test/acp-bridge.test.ts +0 -545
- package/apps/studio/test/acp-capabilities.test.ts +0 -123
- package/apps/studio/test/acp-caps-bridge.test.ts +0 -279
- package/apps/studio/test/acp-commands.test.ts +0 -119
- package/apps/studio/test/acp-elicitation-bridge.test.ts +0 -480
- package/apps/studio/test/acp-elicitation.test.ts +0 -251
- package/apps/studio/test/acp-env.test.ts +0 -95
- package/apps/studio/test/acp-mode-banner.test.ts +0 -45
- package/apps/studio/test/acp-origin-gate.test.ts +0 -160
- package/apps/studio/test/acp-permission-prompt.test.ts +0 -251
- package/apps/studio/test/acp-permission.test.ts +0 -278
- package/apps/studio/test/acp-plugin-bootstrap.test.ts +0 -89
- package/apps/studio/test/acp-session-allowed-tools.test.ts +0 -235
- package/apps/studio/test/acp-session-plugins.test.ts +0 -209
- package/apps/studio/test/acp-toolgroup.test.ts +0 -76
- package/apps/studio/test/acp-transcript-view.test.ts +0 -71
- package/apps/studio/test/acp-transcript.test.ts +0 -238
- package/apps/studio/test/acp-usage-bridge.test.ts +0 -141
- package/apps/studio/test/acp-usage.test.ts +0 -143
- package/apps/studio/test/acp-write-gate.test.ts +0 -323
- package/apps/studio/test/acp-write-scope.test.ts +0 -533
- package/apps/studio/test/active-state.test.ts +0 -186
- package/apps/studio/test/activity.test.ts +0 -189
- package/apps/studio/test/ai-activity.test.ts +0 -113
- package/apps/studio/test/annotate-write.test.ts +0 -1074
- package/apps/studio/test/annotation-strokes-command.test.ts +0 -166
- package/apps/studio/test/annotations-align.test.ts +0 -88
- package/apps/studio/test/annotations-api.test.ts +0 -145
- package/apps/studio/test/annotations-bindings.test.ts +0 -262
- package/apps/studio/test/annotations-draw-modifiers.test.ts +0 -206
- package/apps/studio/test/annotations-groups.test.ts +0 -231
- package/apps/studio/test/annotations-layer.test.ts +0 -1011
- package/apps/studio/test/annotations-roundtrip.test.ts +0 -663
- package/apps/studio/test/annotations-snap.test.ts +0 -135
- package/apps/studio/test/annotations-text-format.test.ts +0 -396
- package/apps/studio/test/artboard-activity-overlay.test.tsx +0 -56
- package/apps/studio/test/artboard-guides-overlay.test.tsx +0 -152
- package/apps/studio/test/artboard-hug-commit.test.ts +0 -36
- package/apps/studio/test/artboard-kinds.test.tsx +0 -83
- package/apps/studio/test/artboard-selection-attrs.test.ts +0 -69
- package/apps/studio/test/asset-api.test.ts +0 -149
- package/apps/studio/test/assets-s3.test.ts +0 -249
- package/apps/studio/test/binary-smoke.test.ts +0 -47
- package/apps/studio/test/boot-self-heal.test.ts +0 -87
- package/apps/studio/test/browse-posture.test.tsx +0 -107
- package/apps/studio/test/bundle-smoke.test.ts +0 -46
- package/apps/studio/test/camera-reveal.test.tsx +0 -173
- package/apps/studio/test/canvas-artifacts.test.ts +0 -125
- package/apps/studio/test/canvas-build-sandbox.test.ts +0 -69
- package/apps/studio/test/canvas-build.test.ts +0 -141
- package/apps/studio/test/canvas-create-api.test.ts +0 -676
- package/apps/studio/test/canvas-cursors.test.ts +0 -164
- package/apps/studio/test/canvas-edit.test.ts +0 -470
- package/apps/studio/test/canvas-freshness-deps.test.ts +0 -64
- package/apps/studio/test/canvas-header.test.ts +0 -127
- package/apps/studio/test/canvas-hide-chrome.test.ts +0 -88
- package/apps/studio/test/canvas-hmr-runtime.test.tsx +0 -114
- package/apps/studio/test/canvas-lib-inline.test.ts +0 -146
- package/apps/studio/test/canvas-lib-motion.test.ts +0 -131
- package/apps/studio/test/canvas-lib-resolver.test.ts +0 -111
- package/apps/studio/test/canvas-list-watch.test.ts +0 -388
- package/apps/studio/test/canvas-media-drop.test.ts +0 -187
- package/apps/studio/test/canvas-meta-api.test.ts +0 -707
- package/apps/studio/test/canvas-move-api.test.ts +0 -587
- package/apps/studio/test/canvas-origin-gate.test.ts +0 -417
- package/apps/studio/test/canvas-pipeline.test.ts +0 -180
- package/apps/studio/test/canvas-rects.test.ts +0 -198
- package/apps/studio/test/canvas-reorder.test.ts +0 -211
- package/apps/studio/test/canvas-route.test.ts +0 -179
- package/apps/studio/test/canvas-shell-base.test.ts +0 -107
- package/apps/studio/test/canvas-shell-build-error.test.ts +0 -52
- package/apps/studio/test/canvas-url.test.ts +0 -138
- package/apps/studio/test/canvas-wheel-delta.test.ts +0 -42
- package/apps/studio/test/canvas-zoom-floor.test.ts +0 -73
- package/apps/studio/test/capture-core.test.ts +0 -32
- package/apps/studio/test/capture-determinism-shape.test.ts +0 -135
- package/apps/studio/test/chat-attachments.test.ts +0 -117
- package/apps/studio/test/chat-context.test.ts +0 -129
- package/apps/studio/test/chat-markdown.test.tsx +0 -58
- package/apps/studio/test/clip-addressing.test.ts +0 -738
- package/apps/studio/test/clip-ops.test.ts +0 -922
- package/apps/studio/test/cloud-build.test.ts +0 -95
- package/apps/studio/test/cloud-connect-note.test.ts +0 -135
- package/apps/studio/test/cloud-endpoints.test.ts +0 -590
- package/apps/studio/test/cloud-history-hardening.test.ts +0 -165
- package/apps/studio/test/cloud-history-posture.test.ts +0 -230
- package/apps/studio/test/cloud-managed-save-surfaces.test.ts +0 -90
- package/apps/studio/test/cloud-renew.test.ts +0 -205
- package/apps/studio/test/cloud-session-role.test.ts +0 -161
- package/apps/studio/test/cloud-shell-surfaces.test.ts +0 -170
- package/apps/studio/test/cold-start-apply.test.ts +0 -303
- package/apps/studio/test/collab-annotations-bridge.test.ts +0 -55
- package/apps/studio/test/collab-awareness-bridge.test.ts +0 -223
- package/apps/studio/test/collab-bridge.test.ts +0 -79
- package/apps/studio/test/collab-loopback.test.ts +0 -63
- package/apps/studio/test/collab-origin-gate.test.ts +0 -323
- package/apps/studio/test/collab-protocol.test.ts +0 -146
- package/apps/studio/test/collab-readonly-gate.test.ts +0 -194
- package/apps/studio/test/collab-reseed-guard.test.ts +0 -78
- package/apps/studio/test/collab-room.test.ts +0 -188
- package/apps/studio/test/collab-session-survival.test.tsx +0 -176
- package/apps/studio/test/collab-stress.test.ts +0 -129
- package/apps/studio/test/comment-mount.test.ts +0 -97
- package/apps/studio/test/comment-relay-origin-gate.test.ts +0 -117
- package/apps/studio/test/comments-api.test.ts +0 -277
- package/apps/studio/test/comments-fs-rebroadcast.test.ts +0 -155
- package/apps/studio/test/comments-overlay.test.ts +0 -117
- package/apps/studio/test/compile-entry.test.ts +0 -133
- package/apps/studio/test/component-map.test.ts +0 -48
- package/apps/studio/test/config-projection.test.ts +0 -104
- package/apps/studio/test/config-reload.test.ts +0 -230
- package/apps/studio/test/config-version.test.ts +0 -88
- package/apps/studio/test/context-resolve-tokens.test.ts +0 -97
- package/apps/studio/test/convert-to-absolute.test.ts +0 -333
- package/apps/studio/test/credential-grammar.test.ts +0 -107
- package/apps/studio/test/csp-canvas-shell.test.ts +0 -62
- package/apps/studio/test/csrf-write-guard.test.ts +0 -141
- package/apps/studio/test/debug-bundle.test.ts +0 -105
- package/apps/studio/test/design-setup-readiness.test.ts +0 -155
- package/apps/studio/test/detach-component.test.ts +0 -94
- package/apps/studio/test/dns-rebinding-guard.test.ts +0 -101
- package/apps/studio/test/dom-selection.test.ts +0 -198
- package/apps/studio/test/dynamic-text-edit.test.ts +0 -162
- package/apps/studio/test/edit-css-occurrence.test.ts +0 -81
- package/apps/studio/test/edit-persistence.test.ts +0 -91
- package/apps/studio/test/edit-scope-api.test.ts +0 -119
- package/apps/studio/test/edit-source-command.test.ts +0 -142
- package/apps/studio/test/edit-suppress.test.ts +0 -170
- package/apps/studio/test/editing-presence.test.ts +0 -103
- package/apps/studio/test/element-resize.test.ts +0 -136
- package/apps/studio/test/element-structural-api.test.ts +0 -434
- package/apps/studio/test/element-structural-edit.test.ts +0 -750
- package/apps/studio/test/ensure-browser.test.ts +0 -92
- package/apps/studio/test/equal-spacing-detector.test.ts +0 -257
- package/apps/studio/test/export-assemble.test.ts +0 -53
- package/apps/studio/test/export-browser-lane.test.ts +0 -126
- package/apps/studio/test/export-capture-fidelity.test.ts +0 -146
- package/apps/studio/test/export-capture-hygiene.test.ts +0 -675
- package/apps/studio/test/export-center.test.tsx +0 -287
- package/apps/studio/test/export-e2e-lanes.test.ts +0 -857
- package/apps/studio/test/export-format-scope-coherence.test.ts +0 -89
- package/apps/studio/test/export-lane.test.ts +0 -245
- package/apps/studio/test/export-shim-multi-capture.test.ts +0 -83
- package/apps/studio/test/exporters/canva.test.ts +0 -63
- package/apps/studio/test/exporters/degraded-propagation.test.ts +0 -123
- package/apps/studio/test/exporters/endpoint.test.ts +0 -121
- package/apps/studio/test/exporters/history.test.ts +0 -107
- package/apps/studio/test/exporters/html.test.ts +0 -26
- package/apps/studio/test/exporters/jobs.test.ts +0 -342
- package/apps/studio/test/exporters/pdf.test.ts +0 -85
- package/apps/studio/test/exporters/png.test.ts +0 -103
- package/apps/studio/test/exporters/pptx-deck.test.ts +0 -112
- package/apps/studio/test/exporters/pptx.test.ts +0 -31
- package/apps/studio/test/exporters/pw-launch.test.ts +0 -49
- package/apps/studio/test/exporters/runtime.test.ts +0 -59
- package/apps/studio/test/exporters/scope.test.ts +0 -292
- package/apps/studio/test/exporters/svg.test.ts +0 -29
- package/apps/studio/test/exporters/unsupported-media.test.ts +0 -123
- package/apps/studio/test/exporters/zip.test.ts +0 -104
- package/apps/studio/test/fetch-asset-gate.test.ts +0 -189
- package/apps/studio/test/figjam-v3-model.test.ts +0 -342
- package/apps/studio/test/figma-explode.test.ts +0 -438
- package/apps/studio/test/figma-provenance.test.ts +0 -108
- package/apps/studio/test/figma-routes.test.ts +0 -294
- package/apps/studio/test/file-lock.test.ts +0 -84
- package/apps/studio/test/fixtures/backgrounding-claude-auth.sh +0 -12
- package/apps/studio/test/fixtures/fake-claude-auth.mjs +0 -23
- package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +0 -1
- package/apps/studio/test/fixtures/mock-acp-agent-caps.mjs +0 -158
- package/apps/studio/test/fixtures/mock-acp-agent-commands.mjs +0 -44
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-flood.mjs +0 -46
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-url.mjs +0 -42
- package/apps/studio/test/fixtures/mock-acp-agent-elicit.mjs +0 -63
- package/apps/studio/test/fixtures/mock-acp-agent-permission-flood.mjs +0 -51
- package/apps/studio/test/fixtures/mock-acp-agent-permission.mjs +0 -50
- package/apps/studio/test/fixtures/mock-acp-agent-slow.mjs +0 -45
- package/apps/studio/test/fixtures/mock-acp-agent-usage.mjs +0 -56
- package/apps/studio/test/fixtures/mock-acp-agent-wedged.mjs +0 -37
- package/apps/studio/test/fixtures/mock-acp-agent-write.mjs +0 -118
- package/apps/studio/test/fixtures/mock-acp-agent.mjs +0 -69
- package/apps/studio/test/fixtures/noisy-claude-auth.mjs +0 -27
- package/apps/studio/test/fixtures/perf-canvas.mjs +0 -201
- package/apps/studio/test/fixtures/phase-20-annotations.svg +0 -1
- package/apps/studio/test/fixtures/phase-21-annotations.svg +0 -1
- package/apps/studio/test/fixtures/tiny-subset.woff2 +0 -0
- package/apps/studio/test/fixtures/video-comp-fixture.tsx +0 -82
- package/apps/studio/test/fixtures/wedged-claude-auth.sh +0 -11
- package/apps/studio/test/footage-store.test.ts +0 -100
- package/apps/studio/test/fs-mkdir-api.test.ts +0 -248
- package/apps/studio/test/fs-watch.test.ts +0 -41
- package/apps/studio/test/gemma-models.test.ts +0 -245
- package/apps/studio/test/generate-route.test.ts +0 -106
- package/apps/studio/test/git-api.test.ts +0 -0
- package/apps/studio/test/git-branches.test.ts +0 -341
- package/apps/studio/test/git-cloud-posture.test.ts +0 -59
- package/apps/studio/test/git-grouping.test.ts +0 -106
- package/apps/studio/test/git-lifecycle.test.ts +0 -100
- package/apps/studio/test/git-log-format.test.ts +0 -95
- package/apps/studio/test/git-safe-rel.test.ts +0 -132
- package/apps/studio/test/git-system-engine.test.ts +0 -135
- package/apps/studio/test/git-watch.test.ts +0 -97
- package/apps/studio/test/github-api.test.ts +0 -577
- package/apps/studio/test/grid-track-handles.test.ts +0 -160
- package/apps/studio/test/handoff-static-frames.test.ts +0 -215
- package/apps/studio/test/handoff.test.ts +0 -329
- package/apps/studio/test/history-rollback.test.ts +0 -88
- package/apps/studio/test/hmr-broadcast.test.ts +0 -236
- package/apps/studio/test/hmr-classify.test.ts +0 -87
- package/apps/studio/test/hub-link.test.ts +0 -80
- package/apps/studio/test/identity-cache.test.ts +0 -83
- package/apps/studio/test/import-asset-browser.test.ts +0 -64
- package/apps/studio/test/import-asset-route.test.ts +0 -71
- package/apps/studio/test/import-asset.test.ts +0 -427
- package/apps/studio/test/import-brand-browser.test.ts +0 -88
- package/apps/studio/test/import-brand-route.test.ts +0 -120
- package/apps/studio/test/import-brand.test.ts +0 -206
- package/apps/studio/test/import-figma.test.ts +0 -667
- package/apps/studio/test/import-tokens.test.ts +0 -722
- package/apps/studio/test/index-data-canvas-ds.test.ts +0 -97
- package/apps/studio/test/input-router.test.ts +0 -757
- package/apps/studio/test/inspect-script-syntax.test.ts +0 -53
- package/apps/studio/test/inspect-selections.test.ts +0 -219
- package/apps/studio/test/knob-props-authored.test.ts +0 -87
- package/apps/studio/test/layers-synthetic-groups.test.ts +0 -96
- package/apps/studio/test/locator.test.ts +0 -214
- package/apps/studio/test/marquee-overlay.test.ts +0 -94
- package/apps/studio/test/media-commit-chain.test.ts +0 -210
- package/apps/studio/test/mode-toggle.test.tsx +0 -226
- package/apps/studio/test/motion-sample.test.ts +0 -35
- package/apps/studio/test/move-artboards-command.test.ts +0 -108
- package/apps/studio/test/participants-chrome.test.ts +0 -65
- package/apps/studio/test/paths.test.ts +0 -88
- package/apps/studio/test/pdf-print-boxes.test.ts +0 -359
- package/apps/studio/test/peer-selection-follows-camera.test.tsx +0 -131
- package/apps/studio/test/perf-harness.ts +0 -193
- package/apps/studio/test/phase-3.6-smoke.test.ts +0 -79
- package/apps/studio/test/photo-bg-remove-validation.test.ts +0 -71
- package/apps/studio/test/photo-canvas-bundle.test.ts +0 -79
- package/apps/studio/test/photo-edit-api.test.ts +0 -201
- package/apps/studio/test/photo-filters.test.ts +0 -149
- package/apps/studio/test/photo-pipeline.test.ts +0 -106
- package/apps/studio/test/photo-taxonomy.test.ts +0 -96
- package/apps/studio/test/print-marks.test.ts +0 -113
- package/apps/studio/test/print-units.test.ts +0 -173
- package/apps/studio/test/read-annotations.test.ts +0 -727
- package/apps/studio/test/read-only-gate.test.ts +0 -188
- package/apps/studio/test/read-only-role.test.ts +0 -95
- package/apps/studio/test/readiness.test.ts +0 -337
- package/apps/studio/test/remote-ahead-behind-cache.test.ts +0 -89
- package/apps/studio/test/reorder-api.test.ts +0 -210
- package/apps/studio/test/repo-concurrency.test.ts +0 -146
- package/apps/studio/test/repo-lock.test.ts +0 -350
- package/apps/studio/test/report-proxy.test.ts +0 -125
- package/apps/studio/test/ripple.test.ts +0 -197
- package/apps/studio/test/runtime-bundle-error-mapping.test.ts +0 -43
- package/apps/studio/test/runtime-bundle.test.ts +0 -69
- package/apps/studio/test/sanitize-annotation-svg.test.ts +0 -238
- package/apps/studio/test/scaffold-design.test.ts +0 -122
- package/apps/studio/test/server-lifecycle.test.ts +0 -38
- package/apps/studio/test/session-runtime-state.test.ts +0 -226
- package/apps/studio/test/session-scope.test.ts +0 -100
- package/apps/studio/test/shared-doc-cell-pairing.test.ts +0 -642
- package/apps/studio/test/shared-doc-convergence.test.ts +0 -265
- package/apps/studio/test/shared-doc-foundation.test.ts +0 -63
- package/apps/studio/test/shared-doc-migrate.test.ts +0 -369
- package/apps/studio/test/shared-doc-projection.test.ts +0 -238
- package/apps/studio/test/shell-importmap.test.ts +0 -56
- package/apps/studio/test/sizing-mode.test.ts +0 -102
- package/apps/studio/test/slash-commands.test.ts +0 -117
- package/apps/studio/test/snap-distance-pill.test.ts +0 -68
- package/apps/studio/test/spacing-handles.test.ts +0 -138
- package/apps/studio/test/specimen-select.test.ts +0 -88
- package/apps/studio/test/sync-agent.test.ts +0 -660
- package/apps/studio/test/sync-annotations-cold-start.test.ts +0 -302
- package/apps/studio/test/sync-asset-push.test.ts +0 -931
- package/apps/studio/test/sync-atomic-write.test.ts +0 -62
- package/apps/studio/test/sync-attach-incremental.test.ts +0 -527
- package/apps/studio/test/sync-autocommit.test.ts +0 -537
- package/apps/studio/test/sync-canvas-path.test.ts +0 -200
- package/apps/studio/test/sync-cell-write-nudge.test.ts +0 -346
- package/apps/studio/test/sync-codec.test.ts +0 -230
- package/apps/studio/test/sync-cold-seed-dedup.test.ts +0 -187
- package/apps/studio/test/sync-cold-start.test.ts +0 -304
- package/apps/studio/test/sync-connect-honesty.test.ts +0 -114
- package/apps/studio/test/sync-connection-state.test.ts +0 -337
- package/apps/studio/test/sync-ctl-channel.test.ts +0 -508
- package/apps/studio/test/sync-decide-file.test.ts +0 -420
- package/apps/studio/test/sync-doc-name.test.ts +0 -281
- package/apps/studio/test/sync-echo-guard.test.ts +0 -96
- package/apps/studio/test/sync-file-ledger.test.ts +0 -334
- package/apps/studio/test/sync-file-membership.test.ts +0 -347
- package/apps/studio/test/sync-file-plane.test.ts +0 -976
- package/apps/studio/test/sync-file-pull.test.ts +0 -333
- package/apps/studio/test/sync-fresh-link-parity.test.ts +0 -267
- package/apps/studio/test/sync-fs-mirror.test.ts +0 -181
- package/apps/studio/test/sync-hardening.test.ts +0 -586
- package/apps/studio/test/sync-hub-listing.test.ts +0 -46
- package/apps/studio/test/sync-hubs-config.test.ts +0 -133
- package/apps/studio/test/sync-incident-replay.test.ts +0 -211
- package/apps/studio/test/sync-journal.test.ts +0 -176
- package/apps/studio/test/sync-meta-codec.test.ts +0 -199
- package/apps/studio/test/sync-migrate-flat-fallback.test.ts +0 -98
- package/apps/studio/test/sync-move-retirement.test.ts +0 -231
- package/apps/studio/test/sync-panel-surface.test.ts +0 -153
- package/apps/studio/test/sync-path-pull.test.ts +0 -531
- package/apps/studio/test/sync-presentation.test.ts +0 -262
- package/apps/studio/test/sync-pull-budget.test.ts +0 -169
- package/apps/studio/test/sync-remote-docs.test.ts +0 -333
- package/apps/studio/test/sync-resync-routes.test.ts +0 -168
- package/apps/studio/test/sync-runtime.test.ts +0 -1675
- package/apps/studio/test/sync-seed-defers-to-hub.test.ts +0 -83
- package/apps/studio/test/sync-settings-routes.test.ts +0 -195
- package/apps/studio/test/sync-settings.test.ts +0 -151
- package/apps/studio/test/sync-status.test.ts +0 -261
- package/apps/studio/test/sync-supervisor.test.ts +0 -262
- package/apps/studio/test/sync-tombstone-apply.test.ts +0 -111
- package/apps/studio/test/sync-trash.test.ts +0 -132
- package/apps/studio/test/sync-two-peer-discovery.test.ts +0 -343
- package/apps/studio/test/sync-untrusted.test.ts +0 -103
- package/apps/studio/test/system-endpoint.test.ts +0 -144
- package/apps/studio/test/text-editability-stamp.test.ts +0 -131
- package/apps/studio/test/timeline-comp-target.test.ts +0 -139
- package/apps/studio/test/timeline-media-cache.test.ts +0 -41
- package/apps/studio/test/timeline-parse.test.ts +0 -184
- package/apps/studio/test/timeline-scale.test.ts +0 -65
- package/apps/studio/test/timeline-snap.test.ts +0 -85
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +0 -86
- package/apps/studio/test/tool-palette.test.tsx +0 -55
- package/apps/studio/test/tour-overlay.test.tsx +0 -90
- package/apps/studio/test/trusted-request-host.test.ts +0 -66
- package/apps/studio/test/undo-sequence-byte-compare.test.ts +0 -211
- package/apps/studio/test/undo-stack.test.ts +0 -244
- package/apps/studio/test/use-agent-presence.test.tsx +0 -117
- package/apps/studio/test/use-annotation-resize.test.ts +0 -402
- package/apps/studio/test/use-annotation-selection.test.tsx +0 -77
- package/apps/studio/test/use-artboard-drag.test.ts +0 -324
- package/apps/studio/test/use-canvas-activity.test.tsx +0 -206
- package/apps/studio/test/use-collab.test.ts +0 -0
- package/apps/studio/test/use-cursor-modifiers.test.ts +0 -59
- package/apps/studio/test/use-keyboard-discipline.test.ts +0 -27
- package/apps/studio/test/use-selection-set.test.tsx +0 -166
- package/apps/studio/test/use-snap-guides.test.ts +0 -271
- package/apps/studio/test/use-tool-mode.test.tsx +0 -186
- package/apps/studio/test/use-undo-stack.test.tsx +0 -223
- package/apps/studio/test/video-asset.test.ts +0 -163
- package/apps/studio/test/video-comp-fixture.test.ts +0 -50
- package/apps/studio/test/video-comp.test.ts +0 -270
- package/apps/studio/test/video-encode-lib.test.ts +0 -63
- package/apps/studio/test/video-render-bridge.test.ts +0 -171
- package/apps/studio/test/whats-new.test.ts +0 -113
- package/apps/studio/test/whisper-setup.test.ts +0 -97
- package/apps/studio/test/workspace-containment.test.ts +0 -382
- package/apps/studio/test/workspace-signin.test.ts +0 -292
- package/apps/studio/test/ws-handshake.test.ts +0 -33
- package/cli/commands/cache.test.mjs +0 -166
- package/cli/commands/design-link.test.mjs +0 -419
- package/cli/commands/design.test.mjs +0 -240
- package/cli/commands/doctor.test.mjs +0 -237
- package/cli/commands/hub-workspace.test.mjs +0 -275
- package/cli/commands/hub.test.mjs +0 -225
- package/cli/commands/kg.test.mjs +0 -142
- package/cli/commands/preflight.test.mjs +0 -46
- package/cli/lib/argv.test.mjs +0 -46
- package/cli/lib/cache.test.mjs +0 -303
- package/cli/lib/cell-plan.test.mjs +0 -225
- package/cli/lib/config-lint.test.mjs +0 -117
- package/cli/lib/ddr-to-kgai.test.mjs +0 -118
- package/cli/lib/design-ownership.test.mjs +0 -329
- package/cli/lib/fetch-asset.test.mjs +0 -213
- package/cli/lib/figma-codegen-reachability.test.mjs +0 -104
- package/cli/lib/figma-import-controls.test.mjs +0 -70
- package/cli/lib/flow-design-integration.test.mjs +0 -165
- package/cli/lib/gitignore-block.test.mjs +0 -136
- package/cli/lib/gitignore-drift.test.mjs +0 -156
- package/cli/lib/hubs-config.test.mjs +0 -165
- package/cli/lib/pkg-root.test.mjs +0 -111
- package/cli/lib/plugin-cli-reachability.test.mjs +0 -57
- package/cli/lib/plugin-name-namespace.test.mjs +0 -71
- package/cli/lib/reconstruct-toolset.test.mjs +0 -194
- package/cli/lib/stack-detect.test.mjs +0 -121
- package/cli/lib/update-check.test.mjs +0 -32
- package/cli/lib/workspace-plan.test.mjs +0 -699
|
@@ -1,707 +0,0 @@
|
|
|
1
|
-
// Phase 4 T5 — `/_api/canvas-meta` GET/PATCH endpoint round-trip.
|
|
2
|
-
// DDR-115 — per-user camera split: `viewport` lives in a gitignored per-machine
|
|
3
|
-
// view file (`_canvas-state/<slug>.view.json`), NEVER inline in the versioned
|
|
4
|
-
// `.meta.json`. PATCH splits the lanes; GET merges them back.
|
|
5
|
-
//
|
|
6
|
-
// Verifies:
|
|
7
|
-
// - PATCH `viewport` leaves `.meta.json` BYTE-UNCHANGED + writes the view file
|
|
8
|
-
// (the mouse-move churn killer) — and a viewport-only patch on a meta-less
|
|
9
|
-
// canvas still succeeds
|
|
10
|
-
// - PATCH clamps zoom to [0.1, 4.0] / rejects non-finite coords (view file)
|
|
11
|
-
// - GET merges the view file's viewport into the returned meta
|
|
12
|
-
// - GET strips a stale inline viewport from a legacy meta
|
|
13
|
-
// - PATCH `layout` writes meta AND bumps `last_modified` (a real shared change)
|
|
14
|
-
// - PATCH layout persists position-only entries (DDR-027)
|
|
15
|
-
// - Paths that escape repoRoot are 404
|
|
16
|
-
|
|
17
|
-
import { describe, expect, test } from 'bun:test';
|
|
18
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
19
|
-
import { join } from 'node:path';
|
|
20
|
-
|
|
21
|
-
import { bootServer, killProc, makeSandbox, nextPort } from './_helpers.ts';
|
|
22
|
-
|
|
23
|
-
interface MetaShape {
|
|
24
|
-
title?: string;
|
|
25
|
-
sections?: unknown[];
|
|
26
|
-
viewport?: { x: number; y: number; zoom: number };
|
|
27
|
-
layout?: { artboards: unknown[] };
|
|
28
|
-
last_modified?: string;
|
|
29
|
-
[k: string]: unknown;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function repoRel(designRoot: string, abs: string): string {
|
|
33
|
-
// designRoot ends in `.design`. repoRoot is its parent.
|
|
34
|
-
const repoRoot = designRoot.replace(/\.design$/, '').replace(/\/+$/, '');
|
|
35
|
-
return abs.startsWith(`${repoRoot}/`) ? abs.slice(repoRoot.length + 1) : abs;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** `_canvas-state/<slug>.view.json` for a `<designRoot>/ui/<Name>.tsx` canvas. */
|
|
39
|
-
function viewPath(designRoot: string, slug: string): string {
|
|
40
|
-
return join(designRoot, '_canvas-state', `${slug}.view.json`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
describe('/_api/canvas-meta — GET/PATCH (DDR-115 camera split)', () => {
|
|
44
|
-
test('PATCH viewport leaves meta byte-unchanged + writes the view file (churn killer)', async () => {
|
|
45
|
-
const { root, designRoot } = makeSandbox();
|
|
46
|
-
const port = nextPort();
|
|
47
|
-
const proc = await bootServer(root, port);
|
|
48
|
-
try {
|
|
49
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
50
|
-
const tsxAbs = join(designRoot, 'ui', 'Phase4.tsx');
|
|
51
|
-
writeFileSync(tsxAbs, 'export default function P(){return <main/>}\n');
|
|
52
|
-
const metaAbs = tsxAbs.replace(/\.tsx$/, '.meta.json');
|
|
53
|
-
writeFileSync(
|
|
54
|
-
metaAbs,
|
|
55
|
-
JSON.stringify({
|
|
56
|
-
title: 'Phase 4',
|
|
57
|
-
sections: [{ id: 'overview', label: 'Overview' }],
|
|
58
|
-
ai_context: { pinned_decisions: ['keep dc-* classes'] },
|
|
59
|
-
})
|
|
60
|
-
);
|
|
61
|
-
const before = readFileSync(metaAbs, 'utf8');
|
|
62
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
63
|
-
|
|
64
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
65
|
-
method: 'PATCH',
|
|
66
|
-
headers: { 'content-type': 'application/json' },
|
|
67
|
-
body: JSON.stringify({ file, patch: { viewport: { x: 12, y: 34, zoom: 1.5 } } }),
|
|
68
|
-
});
|
|
69
|
-
expect(r.status).toBe(200);
|
|
70
|
-
const merged = (await r.json()) as MetaShape;
|
|
71
|
-
// The returned object is GET-shaped: shared keys + the camera merged in.
|
|
72
|
-
expect(merged.title).toBe('Phase 4');
|
|
73
|
-
expect(merged.sections).toBeDefined();
|
|
74
|
-
expect(merged.ai_context).toBeDefined();
|
|
75
|
-
expect(merged.viewport).toEqual({ x: 12, y: 34, zoom: 1.5 });
|
|
76
|
-
|
|
77
|
-
// KEYSTONE — the versioned meta is byte-identical; no viewport, no
|
|
78
|
-
// last_modified bump on a viewport-only patch.
|
|
79
|
-
expect(readFileSync(metaAbs, 'utf8')).toBe(before);
|
|
80
|
-
|
|
81
|
-
// The camera landed in the gitignored per-machine view file.
|
|
82
|
-
const vAbs = viewPath(designRoot, 'ui-phase4');
|
|
83
|
-
expect(existsSync(vAbs)).toBe(true);
|
|
84
|
-
const view = JSON.parse(readFileSync(vAbs, 'utf8'));
|
|
85
|
-
expect(view.viewport).toEqual({ x: 12, y: 34, zoom: 1.5 });
|
|
86
|
-
} finally {
|
|
87
|
-
await killProc(proc);
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('PATCH viewport succeeds on a canvas with no meta yet (writes only the view file)', async () => {
|
|
92
|
-
const { root, designRoot } = makeSandbox();
|
|
93
|
-
const port = nextPort();
|
|
94
|
-
const proc = await bootServer(root, port);
|
|
95
|
-
try {
|
|
96
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
97
|
-
const tsxAbs = join(designRoot, 'ui', 'NoMeta.tsx');
|
|
98
|
-
writeFileSync(tsxAbs, 'export default function N(){return <main/>}\n');
|
|
99
|
-
const metaAbs = tsxAbs.replace(/\.tsx$/, '.meta.json');
|
|
100
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
101
|
-
|
|
102
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
103
|
-
method: 'PATCH',
|
|
104
|
-
headers: { 'content-type': 'application/json' },
|
|
105
|
-
body: JSON.stringify({ file, patch: { viewport: { x: 1, y: 2, zoom: 2 } } }),
|
|
106
|
-
});
|
|
107
|
-
expect(r.status).toBe(200);
|
|
108
|
-
const merged = (await r.json()) as MetaShape;
|
|
109
|
-
expect(merged.viewport).toEqual({ x: 1, y: 2, zoom: 2 });
|
|
110
|
-
// No meta was conjured into existence.
|
|
111
|
-
expect(existsSync(metaAbs)).toBe(false);
|
|
112
|
-
expect(existsSync(viewPath(designRoot, 'ui-nometa'))).toBe(true);
|
|
113
|
-
} finally {
|
|
114
|
-
await killProc(proc);
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test('PATCH clamps zoom to [0.02, 4.0]', async () => {
|
|
119
|
-
const { root, designRoot } = makeSandbox();
|
|
120
|
-
const port = nextPort();
|
|
121
|
-
const proc = await bootServer(root, port);
|
|
122
|
-
try {
|
|
123
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
124
|
-
const tsxAbs = join(designRoot, 'ui', 'Clamp.tsx');
|
|
125
|
-
writeFileSync(tsxAbs, 'export default function C(){return <main/>}\n');
|
|
126
|
-
writeFileSync(tsxAbs.replace(/\.tsx$/, '.meta.json'), '{"title":"Clamp"}');
|
|
127
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
128
|
-
|
|
129
|
-
const r1 = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
130
|
-
method: 'PATCH',
|
|
131
|
-
headers: { 'content-type': 'application/json' },
|
|
132
|
-
body: JSON.stringify({ file, patch: { viewport: { x: 0, y: 0, zoom: 99 } } }),
|
|
133
|
-
});
|
|
134
|
-
const m1 = (await r1.json()) as MetaShape;
|
|
135
|
-
expect(m1.viewport?.zoom).toBe(4);
|
|
136
|
-
|
|
137
|
-
const r2 = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
138
|
-
method: 'PATCH',
|
|
139
|
-
headers: { 'content-type': 'application/json' },
|
|
140
|
-
body: JSON.stringify({ file, patch: { viewport: { x: 0, y: 0, zoom: 0.0001 } } }),
|
|
141
|
-
});
|
|
142
|
-
const m2 = (await r2.json()) as MetaShape;
|
|
143
|
-
expect(m2.viewport?.zoom).toBe(0.02);
|
|
144
|
-
} finally {
|
|
145
|
-
await killProc(proc);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Issue #91 — a board wider than ~10x the viewport needs a zoom below the
|
|
150
|
-
// OLD 0.1 floor to ever be framed whole ("fit to screen") or dragged across
|
|
151
|
-
// in one motion. Before the fix, this value was silently re-clamped up to
|
|
152
|
-
// 0.1 on save, so even a client that could reach 0.05 lost it on reload.
|
|
153
|
-
test('PATCH preserves a deep zoom-out that the old [0.1, 4.0] floor used to clamp away (issue #91)', async () => {
|
|
154
|
-
const { root, designRoot } = makeSandbox();
|
|
155
|
-
const port = nextPort();
|
|
156
|
-
const proc = await bootServer(root, port);
|
|
157
|
-
try {
|
|
158
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
159
|
-
const tsxAbs = join(designRoot, 'ui', 'DeepZoom.tsx');
|
|
160
|
-
writeFileSync(tsxAbs, 'export default function D(){return <main/>}\n');
|
|
161
|
-
writeFileSync(tsxAbs.replace(/\.tsx$/, '.meta.json'), '{"title":"DeepZoom"}');
|
|
162
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
163
|
-
|
|
164
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
165
|
-
method: 'PATCH',
|
|
166
|
-
headers: { 'content-type': 'application/json' },
|
|
167
|
-
body: JSON.stringify({ file, patch: { viewport: { x: 0, y: 0, zoom: 0.05 } } }),
|
|
168
|
-
});
|
|
169
|
-
const m = (await r.json()) as MetaShape;
|
|
170
|
-
expect(m.viewport?.zoom).toBe(0.05);
|
|
171
|
-
|
|
172
|
-
// Round-trips through a fresh GET too (the persisted view file, not just
|
|
173
|
-
// the PATCH response) — this is what a page reload would read back.
|
|
174
|
-
const g = await fetch(
|
|
175
|
-
`http://localhost:${port}/_api/canvas-meta?file=${encodeURIComponent(file)}`
|
|
176
|
-
);
|
|
177
|
-
const gm = (await g.json()) as MetaShape;
|
|
178
|
-
expect(gm.viewport?.zoom).toBe(0.05);
|
|
179
|
-
} finally {
|
|
180
|
-
await killProc(proc);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
test('PATCH ignores non-finite viewport (NaN/Infinity) — keeps the prior camera', async () => {
|
|
185
|
-
const { root, designRoot } = makeSandbox();
|
|
186
|
-
const port = nextPort();
|
|
187
|
-
const proc = await bootServer(root, port);
|
|
188
|
-
try {
|
|
189
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
190
|
-
const tsxAbs = join(designRoot, 'ui', 'Bad.tsx');
|
|
191
|
-
writeFileSync(tsxAbs, 'export default function B(){return <main/>}\n');
|
|
192
|
-
writeFileSync(tsxAbs.replace(/\.tsx$/, '.meta.json'), '{"title":"Bad"}');
|
|
193
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
194
|
-
|
|
195
|
-
// Seed a valid camera (writes the view file).
|
|
196
|
-
await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
197
|
-
method: 'PATCH',
|
|
198
|
-
headers: { 'content-type': 'application/json' },
|
|
199
|
-
body: JSON.stringify({ file, patch: { viewport: { x: 1, y: 2, zoom: 1 } } }),
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
203
|
-
method: 'PATCH',
|
|
204
|
-
headers: { 'content-type': 'application/json' },
|
|
205
|
-
body: JSON.stringify({ file, patch: { viewport: { x: 'nope', y: null, zoom: 1 } } }),
|
|
206
|
-
});
|
|
207
|
-
const m = (await r.json()) as MetaShape;
|
|
208
|
-
// Prior valid camera preserved (the bad patch was a no-op for the view file).
|
|
209
|
-
expect(m.viewport).toEqual({ x: 1, y: 2, zoom: 1 });
|
|
210
|
-
const view = JSON.parse(readFileSync(viewPath(designRoot, 'ui-bad'), 'utf8'));
|
|
211
|
-
expect(view.viewport).toEqual({ x: 1, y: 2, zoom: 1 });
|
|
212
|
-
} finally {
|
|
213
|
-
await killProc(proc);
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test('GET merges the view file viewport into the returned meta', async () => {
|
|
218
|
-
const { root, designRoot } = makeSandbox();
|
|
219
|
-
const port = nextPort();
|
|
220
|
-
const proc = await bootServer(root, port);
|
|
221
|
-
try {
|
|
222
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
223
|
-
const tsxAbs = join(designRoot, 'ui', 'Merge.tsx');
|
|
224
|
-
writeFileSync(tsxAbs, 'export default function M(){return <main/>}\n');
|
|
225
|
-
writeFileSync(tsxAbs.replace(/\.tsx$/, '.meta.json'), JSON.stringify({ title: 'Merge' }));
|
|
226
|
-
mkdirSync(join(designRoot, '_canvas-state'), { recursive: true });
|
|
227
|
-
writeFileSync(
|
|
228
|
-
viewPath(designRoot, 'ui-merge'),
|
|
229
|
-
JSON.stringify({ viewport: { x: 7, y: 8, zoom: 0.5 } })
|
|
230
|
-
);
|
|
231
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
232
|
-
|
|
233
|
-
const r = await fetch(
|
|
234
|
-
`http://localhost:${port}/_api/canvas-meta?file=${encodeURIComponent(file)}`
|
|
235
|
-
);
|
|
236
|
-
expect(r.status).toBe(200);
|
|
237
|
-
const m = (await r.json()) as MetaShape;
|
|
238
|
-
expect(m.title).toBe('Merge');
|
|
239
|
-
expect(m.viewport).toEqual({ x: 7, y: 8, zoom: 0.5 });
|
|
240
|
-
} finally {
|
|
241
|
-
await killProc(proc);
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
test('GET strips a stale inline viewport from a legacy meta (no view file)', async () => {
|
|
246
|
-
const { root, designRoot } = makeSandbox();
|
|
247
|
-
const port = nextPort();
|
|
248
|
-
const proc = await bootServer(root, port);
|
|
249
|
-
try {
|
|
250
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
251
|
-
const tsxAbs = join(designRoot, 'ui', 'Legacy.tsx');
|
|
252
|
-
writeFileSync(tsxAbs, 'export default function L(){return <main/>}\n');
|
|
253
|
-
// A pre-DDR-115 meta with the camera baked inline + a stale last_modified.
|
|
254
|
-
writeFileSync(
|
|
255
|
-
tsxAbs.replace(/\.tsx$/, '.meta.json'),
|
|
256
|
-
JSON.stringify({
|
|
257
|
-
title: 'Legacy',
|
|
258
|
-
viewport: { x: 5, y: 6, zoom: 0.5 },
|
|
259
|
-
last_modified: '2020-01-01T00:00:00.000Z',
|
|
260
|
-
})
|
|
261
|
-
);
|
|
262
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
263
|
-
|
|
264
|
-
const r = await fetch(
|
|
265
|
-
`http://localhost:${port}/_api/canvas-meta?file=${encodeURIComponent(file)}`
|
|
266
|
-
);
|
|
267
|
-
const m = (await r.json()) as MetaShape;
|
|
268
|
-
expect(m.title).toBe('Legacy');
|
|
269
|
-
// The stale inline camera + timestamp are NOT surfaced (no view file → none).
|
|
270
|
-
expect(m.viewport).toBeUndefined();
|
|
271
|
-
expect(m.last_modified).toBeUndefined();
|
|
272
|
-
} finally {
|
|
273
|
-
await killProc(proc);
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
test('PATCH layout writes meta AND bumps last_modified (a real shared change)', async () => {
|
|
278
|
-
const { root, designRoot } = makeSandbox();
|
|
279
|
-
const port = nextPort();
|
|
280
|
-
const proc = await bootServer(root, port);
|
|
281
|
-
try {
|
|
282
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
283
|
-
const tsxAbs = join(designRoot, 'ui', 'Layout.tsx');
|
|
284
|
-
writeFileSync(tsxAbs, 'export default function L(){return <main/>}\n');
|
|
285
|
-
const metaAbs = tsxAbs.replace(/\.tsx$/, '.meta.json');
|
|
286
|
-
writeFileSync(metaAbs, JSON.stringify({ title: 'L', sections: [] }));
|
|
287
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
288
|
-
|
|
289
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
290
|
-
method: 'PATCH',
|
|
291
|
-
headers: { 'content-type': 'application/json' },
|
|
292
|
-
body: JSON.stringify({ file, patch: { layout: { artboards: [{ id: 'a', x: 0, y: 0 }] } } }),
|
|
293
|
-
});
|
|
294
|
-
expect(r.status).toBe(200);
|
|
295
|
-
const onDisk = JSON.parse(readFileSync(metaAbs, 'utf8')) as MetaShape;
|
|
296
|
-
expect(onDisk.layout?.artboards).toBeDefined();
|
|
297
|
-
expect(typeof onDisk.last_modified).toBe('string');
|
|
298
|
-
expect(onDisk.title).toBe('L');
|
|
299
|
-
// The camera never leaks into the versioned meta.
|
|
300
|
-
expect(onDisk.viewport).toBeUndefined();
|
|
301
|
-
} finally {
|
|
302
|
-
await killProc(proc);
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
test('PATCH layout persists position-only entries (Phase 4.2 strip-on-write)', async () => {
|
|
307
|
-
// DDR-027: artboard w/h is JSX-authoritative. The client-side writer
|
|
308
|
-
// (canvas-lib.tsx patchCanvasMeta) strips w/h before PATCH; the server
|
|
309
|
-
// must round-trip whatever shape it receives without rejecting partial
|
|
310
|
-
// entries.
|
|
311
|
-
const { root, designRoot } = makeSandbox();
|
|
312
|
-
const port = nextPort();
|
|
313
|
-
const proc = await bootServer(root, port);
|
|
314
|
-
try {
|
|
315
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
316
|
-
const tsxAbs = join(designRoot, 'ui', 'Phase42.tsx');
|
|
317
|
-
writeFileSync(tsxAbs, 'export default function P(){return <main/>}\n');
|
|
318
|
-
const metaAbs = tsxAbs.replace(/\.tsx$/, '.meta.json');
|
|
319
|
-
writeFileSync(metaAbs, JSON.stringify({ title: 'P', sections: [] }));
|
|
320
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
321
|
-
|
|
322
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
323
|
-
method: 'PATCH',
|
|
324
|
-
headers: { 'content-type': 'application/json' },
|
|
325
|
-
body: JSON.stringify({
|
|
326
|
-
file,
|
|
327
|
-
patch: {
|
|
328
|
-
layout: {
|
|
329
|
-
artboards: [
|
|
330
|
-
{ id: 'a', x: 100, y: 50 },
|
|
331
|
-
{ id: 'b', x: 1500, y: 50 },
|
|
332
|
-
],
|
|
333
|
-
},
|
|
334
|
-
},
|
|
335
|
-
}),
|
|
336
|
-
});
|
|
337
|
-
expect(r.status).toBe(200);
|
|
338
|
-
const merged = (await r.json()) as MetaShape;
|
|
339
|
-
const arts = merged.layout?.artboards as Array<Record<string, unknown>> | undefined;
|
|
340
|
-
expect(arts).toBeDefined();
|
|
341
|
-
expect(arts?.length).toBe(2);
|
|
342
|
-
// No w/h written.
|
|
343
|
-
expect(arts?.[0]).toEqual({ id: 'a', x: 100, y: 50 });
|
|
344
|
-
expect(arts?.[1]).toEqual({ id: 'b', x: 1500, y: 50 });
|
|
345
|
-
|
|
346
|
-
const onDisk = JSON.parse(readFileSync(metaAbs, 'utf8')) as MetaShape;
|
|
347
|
-
const diskArts = onDisk.layout?.artboards as Array<Record<string, unknown>> | undefined;
|
|
348
|
-
expect(diskArts?.[0]).not.toHaveProperty('w');
|
|
349
|
-
expect(diskArts?.[0]).not.toHaveProperty('h');
|
|
350
|
-
} finally {
|
|
351
|
-
await killProc(proc);
|
|
352
|
-
}
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
// Security follow-up to issue #91 — `layout.artboards[]` is reachable from
|
|
356
|
-
// the untrusted canvas origin (DDR-054) and previously had no count/magnitude
|
|
357
|
-
// bound. Now that the zoom-floor fix lets `fit()` correctly frame whatever
|
|
358
|
-
// bounding box a synced layout describes, an unbounded artboard count could
|
|
359
|
-
// promote hundreds/thousands of simultaneous GPU layers on the viewer's
|
|
360
|
-
// client (a DoS). Caps mirror the existing overlays/locked lanes.
|
|
361
|
-
test('PATCH layout rejects an oversized artboards array as a silent no-op', async () => {
|
|
362
|
-
const { root, designRoot } = makeSandbox();
|
|
363
|
-
const port = nextPort();
|
|
364
|
-
const proc = await bootServer(root, port);
|
|
365
|
-
try {
|
|
366
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
367
|
-
const tsxAbs = join(designRoot, 'ui', 'Oversized.tsx');
|
|
368
|
-
writeFileSync(tsxAbs, 'export default function O(){return <main/>}\n');
|
|
369
|
-
const metaAbs = tsxAbs.replace(/\.tsx$/, '.meta.json');
|
|
370
|
-
writeFileSync(metaAbs, JSON.stringify({ title: 'Oversized', sections: [] }));
|
|
371
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
372
|
-
|
|
373
|
-
// Seed a valid, small layout first.
|
|
374
|
-
await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
375
|
-
method: 'PATCH',
|
|
376
|
-
headers: { 'content-type': 'application/json' },
|
|
377
|
-
body: JSON.stringify({ file, patch: { layout: { artboards: [{ id: 'a', x: 0, y: 0 }] } } }),
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
// A crafted, oversized layout (2001 > the 2000-entry cap) must not land.
|
|
381
|
-
const huge = Array.from({ length: 2001 }, (_, i) => ({ id: `a${i}`, x: i, y: 0 }));
|
|
382
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
383
|
-
method: 'PATCH',
|
|
384
|
-
headers: { 'content-type': 'application/json' },
|
|
385
|
-
body: JSON.stringify({ file, patch: { layout: { artboards: huge } } }),
|
|
386
|
-
});
|
|
387
|
-
expect(r.status).toBe(200);
|
|
388
|
-
const merged = (await r.json()) as MetaShape;
|
|
389
|
-
// Silent no-op — the prior small, valid layout is still what's live.
|
|
390
|
-
const arts = merged.layout?.artboards as Array<Record<string, unknown>> | undefined;
|
|
391
|
-
expect(arts).toEqual([{ id: 'a', x: 0, y: 0 }]);
|
|
392
|
-
|
|
393
|
-
const onDisk = JSON.parse(readFileSync(metaAbs, 'utf8')) as MetaShape;
|
|
394
|
-
const diskArts = onDisk.layout?.artboards as Array<Record<string, unknown>> | undefined;
|
|
395
|
-
expect(diskArts).toEqual([{ id: 'a', x: 0, y: 0 }]);
|
|
396
|
-
} finally {
|
|
397
|
-
await killProc(proc);
|
|
398
|
-
}
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
test('PATCH layout rejects an out-of-range artboard coordinate as a silent no-op', async () => {
|
|
402
|
-
const { root, designRoot } = makeSandbox();
|
|
403
|
-
const port = nextPort();
|
|
404
|
-
const proc = await bootServer(root, port);
|
|
405
|
-
try {
|
|
406
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
407
|
-
const tsxAbs = join(designRoot, 'ui', 'OutOfRange.tsx');
|
|
408
|
-
writeFileSync(tsxAbs, 'export default function R(){return <main/>}\n');
|
|
409
|
-
writeFileSync(
|
|
410
|
-
tsxAbs.replace(/\.tsx$/, '.meta.json'),
|
|
411
|
-
JSON.stringify({ title: 'OutOfRange' })
|
|
412
|
-
);
|
|
413
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
414
|
-
|
|
415
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
416
|
-
method: 'PATCH',
|
|
417
|
-
headers: { 'content-type': 'application/json' },
|
|
418
|
-
body: JSON.stringify({
|
|
419
|
-
file,
|
|
420
|
-
patch: { layout: { artboards: [{ id: 'a', x: 50_000_000, y: 0 }] } },
|
|
421
|
-
}),
|
|
422
|
-
});
|
|
423
|
-
expect(r.status).toBe(200);
|
|
424
|
-
const merged = (await r.json()) as MetaShape;
|
|
425
|
-
expect(merged.layout).toBeUndefined();
|
|
426
|
-
} finally {
|
|
427
|
-
await killProc(proc);
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
test('PATCH viewport on a non-existent canvas is refused (404) and mints no file (DDR-115 F-A2)', async () => {
|
|
432
|
-
const { root, designRoot } = makeSandbox();
|
|
433
|
-
const port = nextPort();
|
|
434
|
-
const proc = await bootServer(root, port);
|
|
435
|
-
try {
|
|
436
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
437
|
-
// No .tsx written — the canvas does not exist.
|
|
438
|
-
const file = '.design/ui/Ghost.tsx';
|
|
439
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
440
|
-
method: 'PATCH',
|
|
441
|
-
headers: { 'content-type': 'application/json' },
|
|
442
|
-
body: JSON.stringify({ file, patch: { viewport: { x: 1, y: 2, zoom: 1 } } }),
|
|
443
|
-
});
|
|
444
|
-
expect(r.status).toBe(404);
|
|
445
|
-
// The untrusted-origin write was refused — no per-machine file minted.
|
|
446
|
-
expect(existsSync(viewPath(designRoot, 'ui-ghost'))).toBe(false);
|
|
447
|
-
} finally {
|
|
448
|
-
await killProc(proc);
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
test('PATCH rejects paths that escape repoRoot', async () => {
|
|
453
|
-
const { root } = makeSandbox();
|
|
454
|
-
const port = nextPort();
|
|
455
|
-
const proc = await bootServer(root, port);
|
|
456
|
-
try {
|
|
457
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
458
|
-
method: 'PATCH',
|
|
459
|
-
headers: { 'content-type': 'application/json' },
|
|
460
|
-
body: JSON.stringify({
|
|
461
|
-
file: '../escape.tsx',
|
|
462
|
-
patch: { viewport: { x: 0, y: 0, zoom: 1 } },
|
|
463
|
-
}),
|
|
464
|
-
});
|
|
465
|
-
expect(r.status).toBe(404);
|
|
466
|
-
} finally {
|
|
467
|
-
await killProc(proc);
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
// feature-1-artboard-kinds-foundation, T6 — per-user overlay-visibility lane.
|
|
473
|
-
// Same camera-split contract as `viewport` above: `overlays` lives ONLY in the
|
|
474
|
-
// gitignored per-machine view file, never the versioned `.meta.json`.
|
|
475
|
-
describe('/_api/canvas-meta — overlays lane (T6)', () => {
|
|
476
|
-
test('PATCH overlays leaves meta byte-unchanged + writes the view file', async () => {
|
|
477
|
-
const { root, designRoot } = makeSandbox();
|
|
478
|
-
const port = nextPort();
|
|
479
|
-
const proc = await bootServer(root, port);
|
|
480
|
-
try {
|
|
481
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
482
|
-
const tsxAbs = join(designRoot, 'ui', 'Overlays.tsx');
|
|
483
|
-
writeFileSync(tsxAbs, 'export default function O(){return <main/>}\n');
|
|
484
|
-
const metaAbs = tsxAbs.replace(/\.tsx$/, '.meta.json');
|
|
485
|
-
writeFileSync(metaAbs, JSON.stringify({ title: 'Overlays', sections: [] }));
|
|
486
|
-
const before = readFileSync(metaAbs, 'utf8');
|
|
487
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
488
|
-
|
|
489
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
490
|
-
method: 'PATCH',
|
|
491
|
-
headers: { 'content-type': 'application/json' },
|
|
492
|
-
body: JSON.stringify({ file, patch: { overlays: { guides: true } } }),
|
|
493
|
-
});
|
|
494
|
-
expect(r.status).toBe(200);
|
|
495
|
-
const merged = (await r.json()) as MetaShape;
|
|
496
|
-
expect(merged.overlays).toEqual({ guides: true });
|
|
497
|
-
|
|
498
|
-
// KEYSTONE — versioned meta is byte-identical; no last_modified bump.
|
|
499
|
-
expect(readFileSync(metaAbs, 'utf8')).toBe(before);
|
|
500
|
-
|
|
501
|
-
const vAbs = viewPath(designRoot, 'ui-overlays');
|
|
502
|
-
expect(existsSync(vAbs)).toBe(true);
|
|
503
|
-
const view = JSON.parse(readFileSync(vAbs, 'utf8'));
|
|
504
|
-
expect(view.overlays).toEqual({ guides: true });
|
|
505
|
-
} finally {
|
|
506
|
-
await killProc(proc);
|
|
507
|
-
}
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
test('absent overlays key resolves to hidden (T6 Gotcha — old view files default off)', async () => {
|
|
511
|
-
const { root, designRoot } = makeSandbox();
|
|
512
|
-
const port = nextPort();
|
|
513
|
-
const proc = await bootServer(root, port);
|
|
514
|
-
try {
|
|
515
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
516
|
-
const tsxAbs = join(designRoot, 'ui', 'NoOverlays.tsx');
|
|
517
|
-
writeFileSync(tsxAbs, 'export default function N(){return <main/>}\n');
|
|
518
|
-
writeFileSync(tsxAbs.replace(/\.tsx$/, '.meta.json'), JSON.stringify({ title: 'N' }));
|
|
519
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
520
|
-
|
|
521
|
-
const r = await fetch(
|
|
522
|
-
`http://localhost:${port}/_api/canvas-meta?file=${encodeURIComponent(file)}`
|
|
523
|
-
);
|
|
524
|
-
const m = (await r.json()) as MetaShape;
|
|
525
|
-
expect(m.overlays).toBeUndefined();
|
|
526
|
-
} finally {
|
|
527
|
-
await killProc(proc);
|
|
528
|
-
}
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
test('PATCH overlays merges with an existing viewport in the SAME view file', async () => {
|
|
532
|
-
const { root, designRoot } = makeSandbox();
|
|
533
|
-
const port = nextPort();
|
|
534
|
-
const proc = await bootServer(root, port);
|
|
535
|
-
try {
|
|
536
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
537
|
-
const tsxAbs = join(designRoot, 'ui', 'Both.tsx');
|
|
538
|
-
writeFileSync(tsxAbs, 'export default function B(){return <main/>}\n');
|
|
539
|
-
writeFileSync(tsxAbs.replace(/\.tsx$/, '.meta.json'), JSON.stringify({ title: 'B' }));
|
|
540
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
541
|
-
|
|
542
|
-
await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
543
|
-
method: 'PATCH',
|
|
544
|
-
headers: { 'content-type': 'application/json' },
|
|
545
|
-
body: JSON.stringify({ file, patch: { viewport: { x: 3, y: 4, zoom: 1 } } }),
|
|
546
|
-
});
|
|
547
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
548
|
-
method: 'PATCH',
|
|
549
|
-
headers: { 'content-type': 'application/json' },
|
|
550
|
-
body: JSON.stringify({ file, patch: { overlays: { guides: true } } }),
|
|
551
|
-
});
|
|
552
|
-
const merged = (await r.json()) as MetaShape;
|
|
553
|
-
// Both lanes present — writing overlays didn't clobber the viewport.
|
|
554
|
-
expect(merged.viewport).toEqual({ x: 3, y: 4, zoom: 1 });
|
|
555
|
-
expect(merged.overlays).toEqual({ guides: true });
|
|
556
|
-
|
|
557
|
-
const view = JSON.parse(readFileSync(viewPath(designRoot, 'ui-both'), 'utf8'));
|
|
558
|
-
expect(view.viewport).toEqual({ x: 3, y: 4, zoom: 1 });
|
|
559
|
-
expect(view.overlays).toEqual({ guides: true });
|
|
560
|
-
} finally {
|
|
561
|
-
await killProc(proc);
|
|
562
|
-
}
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
test('PATCH overlays shallow-merges keys — a later patch keeps an earlier key', async () => {
|
|
566
|
-
const { root, designRoot } = makeSandbox();
|
|
567
|
-
const port = nextPort();
|
|
568
|
-
const proc = await bootServer(root, port);
|
|
569
|
-
try {
|
|
570
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
571
|
-
const tsxAbs = join(designRoot, 'ui', 'Merge2.tsx');
|
|
572
|
-
writeFileSync(tsxAbs, 'export default function M(){return <main/>}\n');
|
|
573
|
-
writeFileSync(tsxAbs.replace(/\.tsx$/, '.meta.json'), JSON.stringify({ title: 'M' }));
|
|
574
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
575
|
-
|
|
576
|
-
await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
577
|
-
method: 'PATCH',
|
|
578
|
-
headers: { 'content-type': 'application/json' },
|
|
579
|
-
body: JSON.stringify({ file, patch: { overlays: { guides: true } } }),
|
|
580
|
-
});
|
|
581
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
582
|
-
method: 'PATCH',
|
|
583
|
-
headers: { 'content-type': 'application/json' },
|
|
584
|
-
// A downstream plan (print) toggling ITS OWN key shouldn't erase `guides`.
|
|
585
|
-
body: JSON.stringify({ file, patch: { overlays: { bleed: true } } }),
|
|
586
|
-
});
|
|
587
|
-
const merged = (await r.json()) as MetaShape;
|
|
588
|
-
expect(merged.overlays).toEqual({ guides: true, bleed: true });
|
|
589
|
-
} finally {
|
|
590
|
-
await killProc(proc);
|
|
591
|
-
}
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
test('PATCH ignores a malformed overlays bag (non-boolean value) — silent no-op', async () => {
|
|
595
|
-
const { root, designRoot } = makeSandbox();
|
|
596
|
-
const port = nextPort();
|
|
597
|
-
const proc = await bootServer(root, port);
|
|
598
|
-
try {
|
|
599
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
600
|
-
const tsxAbs = join(designRoot, 'ui', 'Malformed.tsx');
|
|
601
|
-
writeFileSync(tsxAbs, 'export default function X(){return <main/>}\n');
|
|
602
|
-
writeFileSync(tsxAbs.replace(/\.tsx$/, '.meta.json'), JSON.stringify({ title: 'X' }));
|
|
603
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
604
|
-
|
|
605
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
606
|
-
method: 'PATCH',
|
|
607
|
-
headers: { 'content-type': 'application/json' },
|
|
608
|
-
body: JSON.stringify({ file, patch: { overlays: { guides: 'yes' } } }),
|
|
609
|
-
});
|
|
610
|
-
expect(r.status).toBe(200);
|
|
611
|
-
const merged = (await r.json()) as MetaShape;
|
|
612
|
-
expect(merged.overlays).toBeUndefined();
|
|
613
|
-
expect(existsSync(viewPath(designRoot, 'ui-malformed'))).toBe(false);
|
|
614
|
-
} finally {
|
|
615
|
-
await killProc(proc);
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
test('PATCH overlays on a non-existent canvas is refused (404), mints no file', async () => {
|
|
620
|
-
const { root, designRoot } = makeSandbox();
|
|
621
|
-
const port = nextPort();
|
|
622
|
-
const proc = await bootServer(root, port);
|
|
623
|
-
try {
|
|
624
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
625
|
-
const file = '.design/ui/GhostOverlays.tsx';
|
|
626
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
627
|
-
method: 'PATCH',
|
|
628
|
-
headers: { 'content-type': 'application/json' },
|
|
629
|
-
body: JSON.stringify({ file, patch: { overlays: { guides: true } } }),
|
|
630
|
-
});
|
|
631
|
-
expect(r.status).toBe(404);
|
|
632
|
-
expect(existsSync(viewPath(designRoot, 'ui-ghostoverlays'))).toBe(false);
|
|
633
|
-
} finally {
|
|
634
|
-
await killProc(proc);
|
|
635
|
-
}
|
|
636
|
-
});
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
// feature-4 T7b — `locked` (per-user locked layer keys) is the third view-file
|
|
640
|
-
// lane: same split contract as viewport/overlays (never the versioned meta),
|
|
641
|
-
// REPLACE semantics (the client sends the full set), bounded shape.
|
|
642
|
-
describe('/_api/canvas-meta — locked lane (feature-4 T7b)', () => {
|
|
643
|
-
test('PATCH locked leaves meta byte-unchanged + writes the view file; GET merges it back', async () => {
|
|
644
|
-
const { root, designRoot } = makeSandbox();
|
|
645
|
-
const port = nextPort();
|
|
646
|
-
const proc = await bootServer(root, port);
|
|
647
|
-
try {
|
|
648
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
649
|
-
const tsxAbs = join(designRoot, 'ui', 'Locked.tsx');
|
|
650
|
-
writeFileSync(tsxAbs, 'export default function L(){return <main/>}\n');
|
|
651
|
-
const metaAbs = tsxAbs.replace(/\.tsx$/, '.meta.json');
|
|
652
|
-
writeFileSync(metaAbs, JSON.stringify({ title: 'Locked', sections: [] }));
|
|
653
|
-
const before = readFileSync(metaAbs, 'utf8');
|
|
654
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
655
|
-
|
|
656
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
657
|
-
method: 'PATCH',
|
|
658
|
-
headers: { 'content-type': 'application/json' },
|
|
659
|
-
body: JSON.stringify({ file, patch: { locked: ['aabbccdd:0', 'deadbeef:2'] } }),
|
|
660
|
-
});
|
|
661
|
-
expect(r.status).toBe(200);
|
|
662
|
-
const merged = (await r.json()) as MetaShape;
|
|
663
|
-
expect(merged.locked).toEqual(['aabbccdd:0', 'deadbeef:2']);
|
|
664
|
-
// Versioned meta byte-identical (runtime state never lands there).
|
|
665
|
-
expect(readFileSync(metaAbs, 'utf8')).toBe(before);
|
|
666
|
-
const view = JSON.parse(readFileSync(viewPath(designRoot, 'ui-locked'), 'utf8'));
|
|
667
|
-
expect(view.locked).toEqual(['aabbccdd:0', 'deadbeef:2']);
|
|
668
|
-
|
|
669
|
-
// REPLACE semantics — a smaller set unlocks the dropped key.
|
|
670
|
-
await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
671
|
-
method: 'PATCH',
|
|
672
|
-
headers: { 'content-type': 'application/json' },
|
|
673
|
-
body: JSON.stringify({ file, patch: { locked: ['aabbccdd:0'] } }),
|
|
674
|
-
});
|
|
675
|
-
const g = await fetch(
|
|
676
|
-
`http://localhost:${port}/_api/canvas-meta?file=${encodeURIComponent(file)}`
|
|
677
|
-
);
|
|
678
|
-
expect(((await g.json()) as MetaShape).locked).toEqual(['aabbccdd:0']);
|
|
679
|
-
} finally {
|
|
680
|
-
await killProc(proc);
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
|
|
684
|
-
test('malformed locked keys are a silent no-op (shape-gated)', async () => {
|
|
685
|
-
const { root, designRoot } = makeSandbox();
|
|
686
|
-
const port = nextPort();
|
|
687
|
-
const proc = await bootServer(root, port);
|
|
688
|
-
try {
|
|
689
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
690
|
-
const tsxAbs = join(designRoot, 'ui', 'BadLock.tsx');
|
|
691
|
-
writeFileSync(tsxAbs, 'export default function B(){return <main/>}\n');
|
|
692
|
-
writeFileSync(tsxAbs.replace(/\.tsx$/, '.meta.json'), JSON.stringify({ title: 'B' }));
|
|
693
|
-
const file = repoRel(designRoot, tsxAbs);
|
|
694
|
-
await fetch(`http://localhost:${port}/_api/canvas-meta`, {
|
|
695
|
-
method: 'PATCH',
|
|
696
|
-
headers: { 'content-type': 'application/json' },
|
|
697
|
-
body: JSON.stringify({ file, patch: { locked: ['../../etc/passwd', 'not-a-key'] } }),
|
|
698
|
-
});
|
|
699
|
-
const g = await fetch(
|
|
700
|
-
`http://localhost:${port}/_api/canvas-meta?file=${encodeURIComponent(file)}`
|
|
701
|
-
);
|
|
702
|
-
expect(((await g.json()) as MetaShape).locked).toBeUndefined();
|
|
703
|
-
} finally {
|
|
704
|
-
await killProc(proc);
|
|
705
|
-
}
|
|
706
|
-
});
|
|
707
|
-
});
|