@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,111 +0,0 @@
|
|
|
1
|
-
// canvas-lib-resolver — virtual specifier `@maude/canvas-lib` → the dev-server-
|
|
2
|
-
// bundled canvas-lib (Phase 3.6.1 Task 2; relocated to dev-server in Phase
|
|
3
|
-
// 4.0.5 per DDR-025).
|
|
4
|
-
|
|
5
|
-
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
|
6
|
-
import { existsSync } from 'node:fs';
|
|
7
|
-
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
8
|
-
import path from 'node:path';
|
|
9
|
-
|
|
10
|
-
import { buildCanvasModule } from '../canvas-build.ts';
|
|
11
|
-
import {
|
|
12
|
-
CANVAS_LIB_SPECIFIER,
|
|
13
|
-
canvasLibPath,
|
|
14
|
-
readCanvasLibSource,
|
|
15
|
-
} from '../canvas-lib-resolver.ts';
|
|
16
|
-
|
|
17
|
-
const TMP = `/tmp/canvas-lib-resolver-${Math.random().toString(36).slice(2, 8)}`;
|
|
18
|
-
const DESIGN_ROOT = path.join(TMP, 'designRoot');
|
|
19
|
-
|
|
20
|
-
beforeAll(async () => {
|
|
21
|
-
await mkdir(DESIGN_ROOT, { recursive: true });
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
afterAll(async () => {
|
|
25
|
-
await rm(TMP, { recursive: true, force: true });
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
describe('canvas-lib-resolver / canvasLibPath', () => {
|
|
29
|
-
test('returns the dev-server-internal canvas-lib.tsx path', () => {
|
|
30
|
-
const p = canvasLibPath();
|
|
31
|
-
expect(p.endsWith('apps/studio/canvas-lib.tsx')).toBe(true);
|
|
32
|
-
expect(existsSync(p)).toBe(true);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('ignores the legacy designRoot argument (back-compat shim)', () => {
|
|
36
|
-
expect(canvasLibPath('/foo/bar')).toBe(canvasLibPath());
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('CANVAS_LIB_SPECIFIER is `@maude/canvas-lib`', () => {
|
|
40
|
-
expect(CANVAS_LIB_SPECIFIER).toBe('@maude/canvas-lib');
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('canvas-lib-resolver / canvasLibResolver plugin', () => {
|
|
45
|
-
test('resolves @maude/canvas-lib through Bun.build', async () => {
|
|
46
|
-
const canvasPath = path.join(DESIGN_ROOT, 'ui', 'Resolves.tsx');
|
|
47
|
-
await mkdir(path.dirname(canvasPath), { recursive: true });
|
|
48
|
-
const canvasSource =
|
|
49
|
-
`import { DesignCanvas, DCArtboard } from "@maude/canvas-lib";\n` +
|
|
50
|
-
'export default function Demo() {\n' +
|
|
51
|
-
' return <DesignCanvas><DCArtboard>hi</DCArtboard></DesignCanvas>;\n' +
|
|
52
|
-
'}\n';
|
|
53
|
-
await writeFile(canvasPath, canvasSource);
|
|
54
|
-
const r = await buildCanvasModule(canvasPath, canvasSource, {
|
|
55
|
-
designRoot: DESIGN_ROOT,
|
|
56
|
-
});
|
|
57
|
-
// The bundled output should contain the resolved lib's function bodies.
|
|
58
|
-
expect(r.js).toContain('dc-canvas');
|
|
59
|
-
expect(r.js).toContain('dc-artboard');
|
|
60
|
-
// The bare specifier should NOT survive as an import.
|
|
61
|
-
expect(r.js).not.toMatch(/from\s*["']@maude\/canvas-lib["']/);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test('does not interfere with canvases that never import the lib', async () => {
|
|
65
|
-
const canvasPath = path.join(DESIGN_ROOT, 'ui', 'PlainNoLib.tsx');
|
|
66
|
-
await mkdir(path.dirname(canvasPath), { recursive: true });
|
|
67
|
-
const canvasSource = 'export default function Y() { return <button>hi</button>; }\n';
|
|
68
|
-
await writeFile(canvasPath, canvasSource);
|
|
69
|
-
const r = await buildCanvasModule(canvasPath, canvasSource, {
|
|
70
|
-
designRoot: DESIGN_ROOT,
|
|
71
|
-
});
|
|
72
|
-
expect(r.js).toContain('button');
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe('canvas-lib-resolver / readCanvasLibSource', () => {
|
|
77
|
-
test('reads the dev-server-bundled lib source', async () => {
|
|
78
|
-
const src = await readCanvasLibSource();
|
|
79
|
-
// Real bundled lib — assert a stable export name shows up.
|
|
80
|
-
expect(src).toContain('DesignCanvas');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('ignores the legacy designRoot argument', async () => {
|
|
84
|
-
const src = await readCanvasLibSource('/non-existent-design-root');
|
|
85
|
-
expect(src).toContain('DesignCanvas');
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe('canvas-lib-resolver / legacy deprecation guard', () => {
|
|
90
|
-
test('legacy <designRoot>/_lib/canvas-lib.tsx is ignored — dev-server bundled lib wins', async () => {
|
|
91
|
-
const projRoot = path.join(TMP, 'legacy-project');
|
|
92
|
-
const legacyLibDir = path.join(projRoot, '_lib');
|
|
93
|
-
await mkdir(legacyLibDir, { recursive: true });
|
|
94
|
-
// Plant a syntactically-bogus legacy file. If the resolver were still
|
|
95
|
-
// reading from `<designRoot>/_lib/canvas-lib.tsx` this would explode at
|
|
96
|
-
// bundle time; with the dev-server-bundled lib it must be a no-op.
|
|
97
|
-
await writeFile(
|
|
98
|
-
path.join(legacyLibDir, 'canvas-lib.tsx'),
|
|
99
|
-
'// LEGACY — should be ignored\nexport const SHOULD_NOT_APPEAR = true;\n'
|
|
100
|
-
);
|
|
101
|
-
const canvasPath = path.join(projRoot, 'ui', 'Legacy.tsx');
|
|
102
|
-
await mkdir(path.dirname(canvasPath), { recursive: true });
|
|
103
|
-
const canvasSource =
|
|
104
|
-
`import { DesignCanvas } from "@maude/canvas-lib";\n` +
|
|
105
|
-
'export default function L() { return <DesignCanvas>hi</DesignCanvas>; }\n';
|
|
106
|
-
await writeFile(canvasPath, canvasSource);
|
|
107
|
-
const r = await buildCanvasModule(canvasPath, canvasSource, { designRoot: projRoot });
|
|
108
|
-
expect(r.js).toContain('dc-canvas');
|
|
109
|
-
expect(r.js).not.toContain('SHOULD_NOT_APPEAR');
|
|
110
|
-
});
|
|
111
|
-
});
|
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
// canvas-list-watch — external-canvas list watcher. Emits `canvas-list-update`
|
|
2
|
-
// when a canvas file appears/disappears on disk from OUTSIDE the dev-server
|
|
3
|
-
// (the ACP agent's `/design:new`, agent Write, git checkout), so the browser
|
|
4
|
-
// file tree refreshes without a reload.
|
|
5
|
-
// RCA: .ai/logs/rca/issue-acp-new-canvas-not-in-filetree.md
|
|
6
|
-
|
|
7
|
-
import { describe, expect, test } from 'bun:test';
|
|
8
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
9
|
-
import { tmpdir } from 'node:os';
|
|
10
|
-
import { join } from 'node:path';
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
CANVAS_LIST_DEBOUNCE_MS,
|
|
14
|
-
createCanvasListWatch,
|
|
15
|
-
isCanvasCandidate,
|
|
16
|
-
} from '../canvas-list-watch.ts';
|
|
17
|
-
import { type Context, createBus } from '../context.ts';
|
|
18
|
-
import { createFsWatch } from '../fs-watch.ts';
|
|
19
|
-
|
|
20
|
-
const GROUPS = [
|
|
21
|
-
{ label: 'System', path: 'system' },
|
|
22
|
-
{ label: 'UI', path: 'ui' },
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
function sandbox(): { root: string; designRoot: string } {
|
|
26
|
-
const root = mkdtempSync(join(tmpdir(), 'clw-test-'));
|
|
27
|
-
const designRoot = join(root, '.design');
|
|
28
|
-
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
29
|
-
return { root, designRoot };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function mkCtx(designRoot: string): Context {
|
|
33
|
-
return {
|
|
34
|
-
cfg: { canvasGroups: GROUPS } as Context['cfg'],
|
|
35
|
-
projectLabel: '',
|
|
36
|
-
paths: { designRoot, designRel: '.design' } as Context['paths'],
|
|
37
|
-
bus: createBus(),
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const TSX = 'export default function C(){return null}';
|
|
42
|
-
|
|
43
|
-
interface Update {
|
|
44
|
-
action?: string;
|
|
45
|
-
rel?: string;
|
|
46
|
-
slug?: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function collect(ctx: Context): Update[] {
|
|
50
|
-
const got: Update[] = [];
|
|
51
|
-
ctx.bus.on('canvas-list-update', (p) => got.push(p as Update));
|
|
52
|
-
return got;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
describe('canvas-list-watch / isCanvasCandidate (pure gate)', () => {
|
|
56
|
-
const groups = ['system', 'ui'];
|
|
57
|
-
test('accepts openable canvases under a configured group', () => {
|
|
58
|
-
expect(isCanvasCandidate('ui/Pricing.tsx', groups)).toBe(true);
|
|
59
|
-
expect(isCanvasCandidate('ui/project/Pricing.tsx', groups)).toBe(true);
|
|
60
|
-
expect(isCanvasCandidate('ui/Smoke.html', groups)).toBe(true);
|
|
61
|
-
// DS preview specimens DO show in the tree, so they must trigger a refresh.
|
|
62
|
-
expect(isCanvasCandidate('system/project/preview/colors.tsx', groups)).toBe(true);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('rejects non-canvas extensions (sidecars, tokens, docs)', () => {
|
|
66
|
-
expect(isCanvasCandidate('ui/Pricing.meta.json', groups)).toBe(false);
|
|
67
|
-
expect(isCanvasCandidate('ui/Pricing.css', groups)).toBe(false);
|
|
68
|
-
expect(isCanvasCandidate('system/project/README.md', groups)).toBe(false);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('rejects runtime-state (`_`-prefixed segment) + SKIP_DIRS', () => {
|
|
72
|
-
expect(isCanvasCandidate('_history/ui-x/00-screen.tsx', groups)).toBe(false);
|
|
73
|
-
expect(isCanvasCandidate('ui/_draft.tsx', groups)).toBe(false);
|
|
74
|
-
expect(isCanvasCandidate('_active.json', groups)).toBe(false);
|
|
75
|
-
expect(isCanvasCandidate('node_modules/pkg/index.tsx', groups)).toBe(false);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Renaming or moving a folder from Finder / `mv` / a `git checkout` names the
|
|
79
|
-
// DIRECTORY, and that event used to be dropped here — so the tree kept showing
|
|
80
|
-
// the old folder and every canvas inside it silently stopped syncing (its
|
|
81
|
-
// descriptor pointed at a path that no longer existed) until an unrelated
|
|
82
|
-
// `.tsx` write happened to nudge the same recompute.
|
|
83
|
-
test('accepts a directory under a group — a folder rename is a list change', () => {
|
|
84
|
-
expect(isCanvasCandidate('ui/dbox2', groups)).toBe(true);
|
|
85
|
-
expect(isCanvasCandidate('ui/2026/social', groups)).toBe(true);
|
|
86
|
-
expect(isCanvasCandidate('system', groups)).toBe(true);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('a directory still obeys every other rule', () => {
|
|
90
|
-
expect(isCanvasCandidate('ui/_drafts', groups)).toBe(false);
|
|
91
|
-
expect(isCanvasCandidate('node_modules/pkg', groups)).toBe(false);
|
|
92
|
-
expect(isCanvasCandidate('docs/whatever', groups)).toBe(false);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('rejects canvases outside any configured group', () => {
|
|
96
|
-
expect(isCanvasCandidate('docs/Thing.tsx', groups)).toBe(false);
|
|
97
|
-
expect(isCanvasCandidate('Thing.tsx', groups)).toBe(false);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('non-string / empty input is rejected, not thrown', () => {
|
|
101
|
-
expect(isCanvasCandidate('', groups)).toBe(false);
|
|
102
|
-
expect(isCanvasCandidate(undefined as unknown as string, groups)).toBe(false);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
describe('canvas-list-watch / diff + emit', () => {
|
|
107
|
-
test('seeding existing canvases emits nothing', async () => {
|
|
108
|
-
const { root, designRoot } = sandbox();
|
|
109
|
-
writeFileSync(join(designRoot, 'ui', 'Existing.tsx'), TSX);
|
|
110
|
-
const ctx = mkCtx(designRoot);
|
|
111
|
-
const got = collect(ctx);
|
|
112
|
-
const w = createCanvasListWatch(ctx, { debounceMs: 10 });
|
|
113
|
-
await w.ready;
|
|
114
|
-
try {
|
|
115
|
-
expect(got).toHaveLength(0);
|
|
116
|
-
expect(w.known.has('.design/ui/Existing.tsx')).toBe(true);
|
|
117
|
-
} finally {
|
|
118
|
-
w.stop();
|
|
119
|
-
rmSync(root, { recursive: true, force: true });
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('a canvas written straight to disk emits action:added (the bug)', async () => {
|
|
124
|
-
const { root, designRoot } = sandbox();
|
|
125
|
-
const ctx = mkCtx(designRoot);
|
|
126
|
-
const got = collect(ctx);
|
|
127
|
-
const w = createCanvasListWatch(ctx, { debounceMs: 10 });
|
|
128
|
-
await w.ready;
|
|
129
|
-
try {
|
|
130
|
-
// Simulate `/design:new` writing the canvas (and its sidecar) directly —
|
|
131
|
-
// NO API call, so api.ts never emits.
|
|
132
|
-
writeFileSync(join(designRoot, 'ui', 'Pricing.tsx'), TSX);
|
|
133
|
-
writeFileSync(join(designRoot, 'ui', 'Pricing.meta.json'), '{}');
|
|
134
|
-
await w.refresh();
|
|
135
|
-
expect(got).toHaveLength(1);
|
|
136
|
-
expect(got[0]?.action).toBe('added');
|
|
137
|
-
expect(got[0]?.slug).toBe('ui-pricing');
|
|
138
|
-
} finally {
|
|
139
|
-
w.stop();
|
|
140
|
-
rmSync(root, { recursive: true, force: true });
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test('deleting a canvas on disk emits action:removed', async () => {
|
|
145
|
-
const { root, designRoot } = sandbox();
|
|
146
|
-
const ctx = mkCtx(designRoot);
|
|
147
|
-
const got = collect(ctx);
|
|
148
|
-
const w = createCanvasListWatch(ctx, { debounceMs: 10 });
|
|
149
|
-
await w.ready;
|
|
150
|
-
try {
|
|
151
|
-
const abs = join(designRoot, 'ui', 'Gone.tsx');
|
|
152
|
-
writeFileSync(abs, TSX);
|
|
153
|
-
await w.refresh();
|
|
154
|
-
got.length = 0; // drop the 'added'
|
|
155
|
-
rmSync(abs);
|
|
156
|
-
await w.refresh();
|
|
157
|
-
expect(got).toHaveLength(1);
|
|
158
|
-
expect(got[0]?.action).toBe('removed');
|
|
159
|
-
expect(got[0]?.slug).toBe('ui-gone');
|
|
160
|
-
} finally {
|
|
161
|
-
w.stop();
|
|
162
|
-
rmSync(root, { recursive: true, force: true });
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test('editing an existing canvas body emits nothing (no tree thrash)', async () => {
|
|
167
|
-
const { root, designRoot } = sandbox();
|
|
168
|
-
const abs = join(designRoot, 'ui', 'Edited.tsx');
|
|
169
|
-
writeFileSync(abs, TSX);
|
|
170
|
-
const ctx = mkCtx(designRoot);
|
|
171
|
-
const got = collect(ctx);
|
|
172
|
-
const w = createCanvasListWatch(ctx, { debounceMs: 10 });
|
|
173
|
-
await w.ready;
|
|
174
|
-
try {
|
|
175
|
-
writeFileSync(abs, `${TSX}\n// edited`);
|
|
176
|
-
writeFileSync(join(designRoot, 'ui', 'Edited.meta.json'), '{"x":1}');
|
|
177
|
-
await w.refresh();
|
|
178
|
-
expect(got).toHaveLength(0);
|
|
179
|
-
} finally {
|
|
180
|
-
w.stop();
|
|
181
|
-
rmSync(root, { recursive: true, force: true });
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test('runtime-state + sidecar writes never emit', async () => {
|
|
186
|
-
const { root, designRoot } = sandbox();
|
|
187
|
-
const ctx = mkCtx(designRoot);
|
|
188
|
-
const got = collect(ctx);
|
|
189
|
-
const w = createCanvasListWatch(ctx, { debounceMs: 10 });
|
|
190
|
-
await w.ready;
|
|
191
|
-
try {
|
|
192
|
-
writeFileSync(join(designRoot, '_active.json'), '{}');
|
|
193
|
-
mkdirSync(join(designRoot, '_canvas-state'), { recursive: true });
|
|
194
|
-
writeFileSync(join(designRoot, '_canvas-state', 'ui-x.view.json'), '{}');
|
|
195
|
-
writeFileSync(join(designRoot, 'config.json'), '{}');
|
|
196
|
-
await w.refresh();
|
|
197
|
-
expect(got).toHaveLength(0);
|
|
198
|
-
} finally {
|
|
199
|
-
w.stop();
|
|
200
|
-
rmSync(root, { recursive: true, force: true });
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
describe('canvas-list-watch / live fs:any path', () => {
|
|
206
|
-
test('a candidate fs:any event schedules a debounced refresh that emits', async () => {
|
|
207
|
-
const { root, designRoot } = sandbox();
|
|
208
|
-
const ctx = mkCtx(designRoot);
|
|
209
|
-
const got = collect(ctx);
|
|
210
|
-
const w = createCanvasListWatch(ctx, { debounceMs: 20 });
|
|
211
|
-
await w.ready;
|
|
212
|
-
try {
|
|
213
|
-
writeFileSync(join(designRoot, 'ui', 'Live.tsx'), TSX);
|
|
214
|
-
// The fs-watch would emit this; assert the subscriber wires through.
|
|
215
|
-
ctx.bus.emit('fs:any', 'ui/Live.tsx');
|
|
216
|
-
await new Promise((r) => setTimeout(r, 20 + 80));
|
|
217
|
-
await w.refresh(); // flush the chain
|
|
218
|
-
const added = got.find((m) => m.action === 'added' && m.slug === 'ui-live');
|
|
219
|
-
expect(added).toBeTruthy();
|
|
220
|
-
// Exactly one 'added' — the timer + the explicit flush must not double-emit.
|
|
221
|
-
expect(got.filter((m) => m.action === 'added' && m.slug === 'ui-live')).toHaveLength(1);
|
|
222
|
-
} finally {
|
|
223
|
-
w.stop();
|
|
224
|
-
rmSync(root, { recursive: true, force: true });
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
test('a non-candidate fs:any event does not schedule a refresh', async () => {
|
|
229
|
-
const { root, designRoot } = sandbox();
|
|
230
|
-
const ctx = mkCtx(designRoot);
|
|
231
|
-
const got = collect(ctx);
|
|
232
|
-
const w = createCanvasListWatch(ctx, { debounceMs: 20 });
|
|
233
|
-
await w.ready;
|
|
234
|
-
try {
|
|
235
|
-
// Create a canvas but only announce a NON-candidate path. The gate must
|
|
236
|
-
// skip the recompute, so the new canvas stays unseen until a real event.
|
|
237
|
-
writeFileSync(join(designRoot, 'ui', 'Sneaky.tsx'), TSX);
|
|
238
|
-
ctx.bus.emit('fs:any', 'ui/Sneaky.meta.json');
|
|
239
|
-
ctx.bus.emit('fs:any', '_active.json');
|
|
240
|
-
await new Promise((r) => setTimeout(r, 20 + 80));
|
|
241
|
-
expect(got).toHaveLength(0);
|
|
242
|
-
} finally {
|
|
243
|
-
w.stop();
|
|
244
|
-
rmSync(root, { recursive: true, force: true });
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
test('stop() unsubscribes — later events are ignored', async () => {
|
|
249
|
-
const { root, designRoot } = sandbox();
|
|
250
|
-
const ctx = mkCtx(designRoot);
|
|
251
|
-
const got = collect(ctx);
|
|
252
|
-
const w = createCanvasListWatch(ctx, { debounceMs: 10 });
|
|
253
|
-
await w.ready;
|
|
254
|
-
w.stop();
|
|
255
|
-
try {
|
|
256
|
-
writeFileSync(join(designRoot, 'ui', 'After.tsx'), TSX);
|
|
257
|
-
ctx.bus.emit('fs:any', 'ui/After.tsx');
|
|
258
|
-
await new Promise((r) => setTimeout(r, CANVAS_LIST_DEBOUNCE_MS + 40));
|
|
259
|
-
expect(got).toHaveLength(0);
|
|
260
|
-
} finally {
|
|
261
|
-
rmSync(root, { recursive: true, force: true });
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
describe('canvas-list-watch / serialization + multi-remove', () => {
|
|
267
|
-
test('overlapping refresh() calls serialize — a create between them emits exactly once', async () => {
|
|
268
|
-
const { root, designRoot } = sandbox();
|
|
269
|
-
const ctx = mkCtx(designRoot);
|
|
270
|
-
const got = collect(ctx);
|
|
271
|
-
const w = createCanvasListWatch(ctx, { debounceMs: 10 });
|
|
272
|
-
await w.ready;
|
|
273
|
-
try {
|
|
274
|
-
writeFileSync(join(designRoot, 'ui', 'Pair.tsx'), TSX);
|
|
275
|
-
// Fire two refreshes WITHOUT awaiting the first. The promise-chain must
|
|
276
|
-
// serialize them: the first diffs the create against the pre-create set
|
|
277
|
-
// and updates `known`; the second sees no change. Without the chain both
|
|
278
|
-
// would snapshot concurrently against the stale `known` and emit twice.
|
|
279
|
-
const a = w.refresh();
|
|
280
|
-
const b = w.refresh();
|
|
281
|
-
await Promise.all([a, b]);
|
|
282
|
-
expect(got.filter((m) => m.action === 'added' && m.slug === 'ui-pair')).toHaveLength(1);
|
|
283
|
-
} finally {
|
|
284
|
-
w.stop();
|
|
285
|
-
rmSync(root, { recursive: true, force: true });
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
test('removing a whole group subdir emits one removed per canvas (git-checkout shape)', async () => {
|
|
290
|
-
const { root, designRoot } = sandbox();
|
|
291
|
-
mkdirSync(join(designRoot, 'ui', 'project'), { recursive: true });
|
|
292
|
-
writeFileSync(join(designRoot, 'ui', 'project', 'A.tsx'), TSX);
|
|
293
|
-
writeFileSync(join(designRoot, 'ui', 'project', 'B.tsx'), TSX);
|
|
294
|
-
const ctx = mkCtx(designRoot);
|
|
295
|
-
const got = collect(ctx);
|
|
296
|
-
const w = createCanvasListWatch(ctx, { debounceMs: 10 });
|
|
297
|
-
await w.ready;
|
|
298
|
-
try {
|
|
299
|
-
rmSync(join(designRoot, 'ui', 'project'), { recursive: true, force: true });
|
|
300
|
-
await w.refresh();
|
|
301
|
-
const removed = got.filter((m) => m.action === 'removed').map((m) => m.slug);
|
|
302
|
-
expect(removed.sort()).toEqual(['ui-project-a', 'ui-project-b']);
|
|
303
|
-
} finally {
|
|
304
|
-
w.stop();
|
|
305
|
-
rmSync(root, { recursive: true, force: true });
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
describe('canvas-list-watch / real fs-watch end-to-end', () => {
|
|
311
|
-
test('a raw disk write through createFsWatch emits added (the actual bug path)', async () => {
|
|
312
|
-
const { root, designRoot } = sandbox();
|
|
313
|
-
const ctx = mkCtx(designRoot);
|
|
314
|
-
const got = collect(ctx);
|
|
315
|
-
const fsWatch = createFsWatch(ctx);
|
|
316
|
-
const w = createCanvasListWatch(ctx, { debounceMs: 30 });
|
|
317
|
-
await w.ready;
|
|
318
|
-
fsWatch.start();
|
|
319
|
-
try {
|
|
320
|
-
// Let the OS watcher arm, then write straight to disk — NO manual bus
|
|
321
|
-
// emit. This exercises the genuine ACP-agent path: disk write →
|
|
322
|
-
// fs.watch → fs:any → gate → debounce → snapshot diff → canvas-list-update.
|
|
323
|
-
await new Promise((r) => setTimeout(r, 60));
|
|
324
|
-
writeFileSync(join(designRoot, 'ui', 'Real.tsx'), TSX);
|
|
325
|
-
const deadline = Date.now() + 3000;
|
|
326
|
-
while (
|
|
327
|
-
Date.now() < deadline &&
|
|
328
|
-
!got.some((m) => m.action === 'added' && m.slug === 'ui-real')
|
|
329
|
-
) {
|
|
330
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
331
|
-
}
|
|
332
|
-
expect(got.some((m) => m.action === 'added' && m.slug === 'ui-real')).toBe(true);
|
|
333
|
-
} finally {
|
|
334
|
-
fsWatch.stop();
|
|
335
|
-
w.stop();
|
|
336
|
-
rmSync(root, { recursive: true, force: true });
|
|
337
|
-
}
|
|
338
|
-
}, 10_000);
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
describe('canvas-list-watch / config hot-reload (stale-config RCA)', () => {
|
|
342
|
-
// /design:setup-ds adds the `system` group to config.json mid-session;
|
|
343
|
-
// reloadConfig (context.ts) swaps ctx.cfg in place. The watcher must read
|
|
344
|
-
// groups at USE time — a boot-captured groupPaths list would keep gating
|
|
345
|
-
// the new group's files out forever.
|
|
346
|
-
// RCA: .ai/logs/rca/issue-ds-scaffold-files-not-in-filetree-stale-config.md
|
|
347
|
-
test('a group added by an in-place cfg swap uncovers its canvases', async () => {
|
|
348
|
-
const { root, designRoot } = sandbox();
|
|
349
|
-
mkdirSync(join(designRoot, 'system', 'kanban-glass', 'preview'), { recursive: true });
|
|
350
|
-
const ctx = mkCtx(designRoot);
|
|
351
|
-
// Pre-scaffold fixture shape: UI-only group.
|
|
352
|
-
ctx.cfg.canvasGroups = [{ label: 'UI', path: 'ui' }];
|
|
353
|
-
const got = collect(ctx);
|
|
354
|
-
const w = createCanvasListWatch(ctx, { debounceMs: 10 });
|
|
355
|
-
await w.ready;
|
|
356
|
-
try {
|
|
357
|
-
// Scaffold writes specimens while the group is still absent — no emit.
|
|
358
|
-
writeFileSync(join(designRoot, 'system', 'kanban-glass', 'preview', 'colors.tsx'), TSX);
|
|
359
|
-
ctx.bus.emit('fs:any', 'system/kanban-glass/preview/colors.tsx');
|
|
360
|
-
await new Promise((r) => setTimeout(r, 10 + 40));
|
|
361
|
-
await w.refresh();
|
|
362
|
-
expect(got).toHaveLength(0);
|
|
363
|
-
|
|
364
|
-
// Hot reload: new array on the SAME cfg object (what reloadConfig does),
|
|
365
|
-
// then the server.ts subscriber calls refresh() — the diff must surface
|
|
366
|
-
// the pre-existing specimen.
|
|
367
|
-
ctx.cfg.canvasGroups = [
|
|
368
|
-
{ label: 'Design system', path: 'system' },
|
|
369
|
-
{ label: 'UI', path: 'ui' },
|
|
370
|
-
];
|
|
371
|
-
await w.refresh();
|
|
372
|
-
expect(got).toHaveLength(1);
|
|
373
|
-
expect(got[0]?.action).toBe('added');
|
|
374
|
-
expect(got[0]?.rel).toBe('system/kanban-glass/preview/colors.tsx');
|
|
375
|
-
|
|
376
|
-
// And FUTURE writes under the new group pass the live fs:any gate.
|
|
377
|
-
got.length = 0;
|
|
378
|
-
writeFileSync(join(designRoot, 'system', 'kanban-glass', 'preview', 'type.tsx'), TSX);
|
|
379
|
-
ctx.bus.emit('fs:any', 'system/kanban-glass/preview/type.tsx');
|
|
380
|
-
await new Promise((r) => setTimeout(r, 10 + 40));
|
|
381
|
-
await w.refresh();
|
|
382
|
-
expect(got.some((m) => m.action === 'added' && m.rel?.endsWith('type.tsx'))).toBe(true);
|
|
383
|
-
} finally {
|
|
384
|
-
w.stop();
|
|
385
|
-
rmSync(root, { recursive: true, force: true });
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
});
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
// use-canvas-media-drop — Phase 23 pure helpers (no DOM).
|
|
2
|
-
//
|
|
3
|
-
// Covers the drop/paste dispatch matrix + the URL helpers. The http(s) scheme
|
|
4
|
-
// gate (isHttpUrl) is security-relevant — a link chip must never carry a
|
|
5
|
-
// javascript:/data: URL into window.open.
|
|
6
|
-
|
|
7
|
-
import { describe, expect, test } from 'bun:test';
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
anchorTextFromHtml,
|
|
11
|
-
classifyMediaPayload,
|
|
12
|
-
firstHttpUrl,
|
|
13
|
-
isHttpUrl,
|
|
14
|
-
linkDomain,
|
|
15
|
-
type MediaPayload,
|
|
16
|
-
mediaSnippet,
|
|
17
|
-
normalizeUrl,
|
|
18
|
-
prettifyUrl,
|
|
19
|
-
} from '../use-canvas-media-drop.tsx';
|
|
20
|
-
|
|
21
|
-
const empty: MediaPayload = { files: [], uriList: '', html: '', plain: '' };
|
|
22
|
-
const fakeFile = (type: string): File => ({ type, name: 'x', size: 1 }) as unknown as File;
|
|
23
|
-
|
|
24
|
-
describe('use-canvas-media-drop / isHttpUrl (scheme gate)', () => {
|
|
25
|
-
test('accepts http + https only', () => {
|
|
26
|
-
expect(isHttpUrl('https://example.com')).toBe(true);
|
|
27
|
-
expect(isHttpUrl('http://example.com/a/b?c=1')).toBe(true);
|
|
28
|
-
});
|
|
29
|
-
test('rejects javascript: / data: / file: / mailto: / garbage', () => {
|
|
30
|
-
expect(isHttpUrl('javascript:alert(1)')).toBe(false);
|
|
31
|
-
expect(isHttpUrl('data:text/html,<script>')).toBe(false);
|
|
32
|
-
expect(isHttpUrl('file:///etc/passwd')).toBe(false);
|
|
33
|
-
expect(isHttpUrl('mailto:a@b.com')).toBe(false);
|
|
34
|
-
expect(isHttpUrl('not a url')).toBe(false);
|
|
35
|
-
expect(isHttpUrl('')).toBe(false);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe('use-canvas-media-drop / normalizeUrl (no scheme required)', () => {
|
|
40
|
-
test('keeps an explicit http(s) URL', () => {
|
|
41
|
-
expect(normalizeUrl('https://example.com/x')).toBe('https://example.com/x');
|
|
42
|
-
expect(normalizeUrl('http://example.com')).toBe('http://example.com');
|
|
43
|
-
});
|
|
44
|
-
test('prepends https:// to a bare domain', () => {
|
|
45
|
-
expect(normalizeUrl('example.com')).toBe('https://example.com');
|
|
46
|
-
expect(normalizeUrl('www.example.com/blog/post?x=1')).toBe(
|
|
47
|
-
'https://www.example.com/blog/post?x=1'
|
|
48
|
-
);
|
|
49
|
-
expect(normalizeUrl('sub.example.io:8080/path')).toBe('https://sub.example.io:8080/path');
|
|
50
|
-
});
|
|
51
|
-
test('rejects dangerous schemes even though it is lenient about http(s)', () => {
|
|
52
|
-
expect(normalizeUrl('javascript:alert(1)')).toBeNull();
|
|
53
|
-
expect(normalizeUrl('data:text/html,x')).toBeNull();
|
|
54
|
-
expect(normalizeUrl('file:///etc/passwd')).toBeNull();
|
|
55
|
-
expect(normalizeUrl('mailto:a@b.com')).toBeNull();
|
|
56
|
-
});
|
|
57
|
-
test('does NOT mistake a bare decimal / non-domain for a URL', () => {
|
|
58
|
-
expect(normalizeUrl('3.5')).toBeNull();
|
|
59
|
-
expect(normalizeUrl('v1.2')).toBeNull();
|
|
60
|
-
expect(normalizeUrl('hello world')).toBeNull();
|
|
61
|
-
expect(normalizeUrl('')).toBeNull();
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe('use-canvas-media-drop / firstHttpUrl', () => {
|
|
66
|
-
test('a bare-domain line becomes an https URL (no scheme needed to paste)', () => {
|
|
67
|
-
expect(firstHttpUrl('example.com')).toBe('https://example.com');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('skips uri-list comment lines and returns the first real URL', () => {
|
|
71
|
-
expect(firstHttpUrl('# a comment\nhttps://example.com/p\nhttps://other.com')).toBe(
|
|
72
|
-
'https://example.com/p'
|
|
73
|
-
);
|
|
74
|
-
});
|
|
75
|
-
test('finds an inline URL in a prose paste', () => {
|
|
76
|
-
expect(firstHttpUrl('see https://example.com/post for details')).toBe(
|
|
77
|
-
'https://example.com/post'
|
|
78
|
-
);
|
|
79
|
-
});
|
|
80
|
-
test('returns null for non-URL text', () => {
|
|
81
|
-
expect(firstHttpUrl('just some words')).toBeNull();
|
|
82
|
-
expect(firstHttpUrl('javascript:alert(1)')).toBeNull();
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
describe('use-canvas-media-drop / linkDomain + prettifyUrl', () => {
|
|
87
|
-
test('linkDomain strips www.', () => {
|
|
88
|
-
expect(linkDomain('https://www.example.com/x')).toBe('example.com');
|
|
89
|
-
expect(linkDomain('https://sub.example.io')).toBe('sub.example.io');
|
|
90
|
-
});
|
|
91
|
-
test('prettifyUrl yields host + path (no protocol, no trailing slash)', () => {
|
|
92
|
-
expect(prettifyUrl('https://www.example.com/blog/post/')).toBe('example.com/blog/post');
|
|
93
|
-
expect(prettifyUrl('https://example.com/')).toBe('example.com');
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
describe('use-canvas-media-drop / anchorTextFromHtml', () => {
|
|
98
|
-
test('extracts the anchor text from a text/html payload', () => {
|
|
99
|
-
expect(anchorTextFromHtml('<a href="https://x.io">Cool Post</a>')).toBe('Cool Post');
|
|
100
|
-
});
|
|
101
|
-
test('strips inner markup and collapses whitespace', () => {
|
|
102
|
-
expect(anchorTextFromHtml('<a href="#"><b>Bold</b> title</a>')).toBe('Bold title');
|
|
103
|
-
});
|
|
104
|
-
test('returns null when there is no anchor', () => {
|
|
105
|
-
expect(anchorTextFromHtml('<div>no link</div>')).toBeNull();
|
|
106
|
-
expect(anchorTextFromHtml('')).toBeNull();
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
describe('use-canvas-media-drop / classifyMediaPayload (dispatch matrix)', () => {
|
|
111
|
-
test('an image file wins (even when a uri-list is also present)', () => {
|
|
112
|
-
const intent = classifyMediaPayload({
|
|
113
|
-
...empty,
|
|
114
|
-
files: [fakeFile('image/png')],
|
|
115
|
-
uriList: 'https://example.com',
|
|
116
|
-
});
|
|
117
|
-
expect(intent?.kind).toBe('image');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test('a non-image, non-media file is ignored', () => {
|
|
121
|
-
expect(classifyMediaPayload({ ...empty, files: [fakeFile('application/pdf')] })).toBeNull();
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('a video file → a media intent (DDR-148)', () => {
|
|
125
|
-
const intent = classifyMediaPayload({ ...empty, files: [fakeFile('video/mp4')] });
|
|
126
|
-
expect(intent).toEqual({ kind: 'media', file: expect.anything(), mediaKind: 'video' });
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test('an audio file → a media intent', () => {
|
|
130
|
-
const intent = classifyMediaPayload({ ...empty, files: [fakeFile('audio/mpeg')] });
|
|
131
|
-
expect(intent?.kind).toBe('media');
|
|
132
|
-
expect(intent && intent.kind === 'media' && intent.mediaKind).toBe('audio');
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test('an image still wins over a co-dropped video', () => {
|
|
136
|
-
const intent = classifyMediaPayload({
|
|
137
|
-
...empty,
|
|
138
|
-
files: [fakeFile('image/png'), fakeFile('video/mp4')],
|
|
139
|
-
});
|
|
140
|
-
expect(intent?.kind).toBe('image');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test('mediaSnippet emits the right Remotion element', () => {
|
|
144
|
-
expect(mediaSnippet('video', 'assets/ab12cd34.mp4')).toBe(
|
|
145
|
-
'<Video src="assets/ab12cd34.mp4" />'
|
|
146
|
-
);
|
|
147
|
-
expect(mediaSnippet('audio', 'assets/ff00aa11.mp3')).toBe(
|
|
148
|
-
'<Audio src="assets/ff00aa11.mp3" />'
|
|
149
|
-
);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test('a URL drop becomes a link with the anchor text as the title', () => {
|
|
153
|
-
const intent = classifyMediaPayload({
|
|
154
|
-
...empty,
|
|
155
|
-
uriList: 'https://example.com/post',
|
|
156
|
-
html: '<a href="https://example.com/post">Read this</a>',
|
|
157
|
-
});
|
|
158
|
-
expect(intent).toEqual({ kind: 'link', url: 'https://example.com/post', title: 'Read this' });
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test('a URL drop with no anchor text falls back to the prettified URL', () => {
|
|
162
|
-
const intent = classifyMediaPayload({ ...empty, uriList: 'https://example.com/post' });
|
|
163
|
-
expect(intent).toEqual({
|
|
164
|
-
kind: 'link',
|
|
165
|
-
url: 'https://example.com/post',
|
|
166
|
-
title: 'example.com/post',
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test('a bare-domain paste (no scheme) becomes a link normalized to https', () => {
|
|
171
|
-
const intent = classifyMediaPayload({ ...empty, plain: 'example.com/post' });
|
|
172
|
-
expect(intent).toEqual({
|
|
173
|
-
kind: 'link',
|
|
174
|
-
url: 'https://example.com/post',
|
|
175
|
-
title: 'example.com/post',
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test('a plain-text (non-media) paste classifies as null — caller leaves it alone', () => {
|
|
180
|
-
expect(classifyMediaPayload({ ...empty, plain: 'hello world' })).toBeNull();
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test('a javascript: URL is never classified as a link', () => {
|
|
184
|
-
expect(classifyMediaPayload({ ...empty, plain: 'javascript:alert(1)' })).toBeNull();
|
|
185
|
-
expect(classifyMediaPayload({ ...empty, uriList: 'javascript:alert(1)' })).toBeNull();
|
|
186
|
-
});
|
|
187
|
-
});
|