@1agh/maude 1.0.11 → 1.3.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/README.md +3 -1
- package/apps/studio/acp/bridge.ts +18 -13
- package/apps/studio/acp/index.ts +43 -7
- package/apps/studio/acp/transcript-io.ts +394 -0
- package/apps/studio/acp/transcript.ts +103 -38
- package/apps/studio/annotations-layer.tsx +191 -116
- package/apps/studio/annotations-model.ts +39 -0
- package/apps/studio/annotations-sync.ts +50 -0
- package/apps/studio/api.ts +821 -55
- package/apps/studio/bin/_import-figma.mjs +57 -11
- package/apps/studio/bin/_seed-fixture.mjs +163 -0
- package/apps/studio/bin/annotate.mjs +3 -1
- package/apps/studio/bin/server-up.sh +17 -2
- package/apps/studio/canvas-edit.ts +391 -7
- package/apps/studio/canvas-imports.ts +89 -0
- package/apps/studio/canvas-lib.tsx +82 -18
- package/apps/studio/canvas-notice-message.ts +16 -0
- package/apps/studio/canvas-notifications.tsx +19 -0
- package/apps/studio/canvas-shell.tsx +57 -6
- package/apps/studio/client/app.jsx +943 -248
- package/apps/studio/client/apply-edit-request.ts +55 -0
- package/apps/studio/client/export-center.jsx +125 -67
- package/apps/studio/client/github.js +38 -1
- package/apps/studio/client/index-loader.ts +76 -0
- package/apps/studio/client/panels/CloudBar.jsx +52 -6
- package/apps/studio/client/panels/GitPanel.jsx +103 -0
- package/apps/studio/client/panels/OnboardingWizard.jsx +36 -7
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +92 -27
- package/apps/studio/client/panels/SourceConflictPanel.jsx +166 -0
- package/apps/studio/client/panels/SyncPanel.jsx +238 -4
- package/apps/studio/client/panels/TeamProjects.jsx +376 -0
- package/apps/studio/client/panels/file-deep-link-dialog.jsx +54 -0
- package/apps/studio/client/photo-knobs.jsx +3 -0
- package/apps/studio/client/report-bug.jsx +19 -1
- package/apps/studio/client/share-dialog.jsx +91 -0
- package/apps/studio/client/share-link.js +94 -0
- package/apps/studio/client/styles/3-shell-maude.css +69 -18
- package/apps/studio/client/styles/4-components.css +44 -1
- package/apps/studio/client/tree-row-menu.jsx +48 -3
- package/apps/studio/client/whats-new.jsx +26 -32
- package/apps/studio/cloud/endpoints.ts +132 -0
- package/apps/studio/collab/index.ts +4 -0
- package/apps/studio/collab/persistence.ts +137 -17
- package/apps/studio/collab/registry.ts +19 -21
- package/apps/studio/collab/room.ts +13 -2
- package/apps/studio/context.ts +59 -0
- package/apps/studio/debug-bundle.ts +13 -0
- package/apps/studio/dist/client.bundle.js +1911 -1879
- package/apps/studio/dist/runtime/.min-sizes.json +1 -0
- package/apps/studio/dist/runtime/sonner.js +1 -0
- 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 +296 -19
- package/apps/studio/inspect.ts +19 -0
- package/apps/studio/managed-projects.ts +150 -0
- package/apps/studio/mem.ts +45 -9
- package/apps/studio/notifications.tsx +292 -0
- package/apps/studio/runtime-bundle.ts +2 -0
- package/apps/studio/server.ts +69 -6
- package/apps/studio/sync/accepted-cold-start.ts +225 -0
- package/apps/studio/sync/accepted-link.ts +320 -0
- package/apps/studio/sync/action-stage.ts +343 -0
- package/apps/studio/sync/agent.ts +104 -45
- package/apps/studio/sync/asset-push.ts +1 -47
- package/apps/studio/sync/cell-file-events.ts +3 -0
- package/apps/studio/sync/codec.ts +192 -34
- 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/ctl-provider.ts +15 -2
- package/apps/studio/sync/document-discovery.ts +81 -0
- package/apps/studio/sync/file-ledger.ts +117 -0
- package/apps/studio/sync/file-membership.ts +10 -2
- package/apps/studio/sync/file-plane.ts +926 -47
- package/apps/studio/sync/index.ts +1786 -36
- package/apps/studio/sync/journal-client.ts +13 -1
- package/apps/studio/sync/migrate-seed.ts +150 -22
- package/apps/studio/sync/poke.ts +4 -2
- package/apps/studio/sync/presentation.ts +311 -5
- package/apps/studio/sync/projection.ts +820 -23
- package/apps/studio/sync/repeated-module.ts +73 -0
- package/apps/studio/sync/retry-after.ts +169 -0
- package/apps/studio/sync/revision-barrier.ts +129 -0
- package/apps/studio/sync/seed-progress.ts +218 -0
- package/apps/studio/sync/seed-repair.ts +46 -0
- package/apps/studio/sync/source-merge.ts +100 -0
- package/apps/studio/sync/source-ops.ts +289 -0
- package/apps/studio/sync/source-recovery.ts +70 -0
- package/apps/studio/sync/source-validation.ts +56 -0
- package/apps/studio/sync/status.ts +201 -6
- package/apps/studio/sync/transaction-client.ts +460 -0
- package/apps/studio/sync/transport-error.ts +111 -0
- package/apps/studio/sync/writer-registry.ts +236 -0
- package/apps/studio/text-caret.ts +35 -0
- package/apps/studio/undo-hud.tsx +9 -87
- package/apps/studio/use-canvas-media-drop.tsx +4 -39
- package/apps/studio/whats-new.json +173 -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 +892 -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 +33 -9
- package/plugins/design/dependencies.json +3 -3
- package/plugins/design/templates/_shell.html +47 -0
- package/plugins/flow/.claude-plugin/config.schema.json +8 -0
- package/plugins/flow/dependencies.json +3 -3
- 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,350 +0,0 @@
|
|
|
1
|
-
// Cloud Phase 27 D2 — one writer at a time on the workspace checkout.
|
|
2
|
-
//
|
|
3
|
-
// The lock is the thing standing between "two processes over one working tree"
|
|
4
|
-
// and the preserved dissent's 3 a.m. event, so the properties asserted here are
|
|
5
|
-
// the ones that make it a lock rather than a suggestion: mutual exclusion,
|
|
6
|
-
// release on throw, and a stale holder that unwedges itself without a human.
|
|
7
|
-
|
|
8
|
-
import { describe, expect, test } from 'bun:test';
|
|
9
|
-
import {
|
|
10
|
-
mkdirSync,
|
|
11
|
-
mkdtempSync,
|
|
12
|
-
readdirSync,
|
|
13
|
-
readFileSync,
|
|
14
|
-
rmSync,
|
|
15
|
-
utimesSync,
|
|
16
|
-
writeFileSync,
|
|
17
|
-
} from 'node:fs';
|
|
18
|
-
import { tmpdir } from 'node:os';
|
|
19
|
-
import { join } from 'node:path';
|
|
20
|
-
|
|
21
|
-
import { HEARTBEAT_MS, REPO_LOCK_FILE, STALE_LOCK_MS, withRepoLock } from '../git/repo-lock.ts';
|
|
22
|
-
|
|
23
|
-
function makeRepo({ git = true } = {}): string {
|
|
24
|
-
const root = mkdtempSync(join(tmpdir(), 'maude-repo-lock-'));
|
|
25
|
-
if (git) mkdirSync(join(root, '.git'), { recursive: true });
|
|
26
|
-
return root;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const lockFile = (root: string) => join(root, '.git', REPO_LOCK_FILE);
|
|
30
|
-
const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
|
31
|
-
|
|
32
|
-
describe('repo lock', () => {
|
|
33
|
-
test('two holders never overlap — the loser waits rather than failing', async () => {
|
|
34
|
-
const root = makeRepo();
|
|
35
|
-
try {
|
|
36
|
-
const order: string[] = [];
|
|
37
|
-
let concurrent = 0;
|
|
38
|
-
let maxConcurrent = 0;
|
|
39
|
-
|
|
40
|
-
const contend = (name: string) =>
|
|
41
|
-
withRepoLock(root, name, async () => {
|
|
42
|
-
concurrent += 1;
|
|
43
|
-
maxConcurrent = Math.max(maxConcurrent, concurrent);
|
|
44
|
-
order.push(`${name}:in`);
|
|
45
|
-
await sleep(30);
|
|
46
|
-
order.push(`${name}:out`);
|
|
47
|
-
concurrent -= 1;
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
await Promise.all([contend('hub:autocommit'), contend('studio:checkout')]);
|
|
51
|
-
|
|
52
|
-
// The point of the lock, stated twice: never two inside, and never an
|
|
53
|
-
// interleaved in/in/out/out — the second entry follows the first exit.
|
|
54
|
-
expect(maxConcurrent).toBe(1);
|
|
55
|
-
expect(order[1]).toBe(order[0].replace(':in', ':out'));
|
|
56
|
-
} finally {
|
|
57
|
-
rmSync(root, { recursive: true, force: true });
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('the lock is released when the operation throws', async () => {
|
|
62
|
-
const root = makeRepo();
|
|
63
|
-
try {
|
|
64
|
-
await expect(
|
|
65
|
-
withRepoLock(root, 'studio:commit', async () => {
|
|
66
|
-
throw new Error('git exploded');
|
|
67
|
-
})
|
|
68
|
-
).rejects.toThrow('git exploded');
|
|
69
|
-
|
|
70
|
-
// Not merely "the file is gone" — the next caller gets in, which is the
|
|
71
|
-
// property that matters. A lock leaked by a failed commit would stop
|
|
72
|
-
// every autosave after it.
|
|
73
|
-
let ran = false;
|
|
74
|
-
await withRepoLock(root, 'hub:autocommit', async () => {
|
|
75
|
-
ran = true;
|
|
76
|
-
});
|
|
77
|
-
expect(ran).toBe(true);
|
|
78
|
-
expect(() => readFileSync(lockFile(root))).toThrow();
|
|
79
|
-
} finally {
|
|
80
|
-
rmSync(root, { recursive: true, force: true });
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('a repo with no .git runs unlocked instead of creating one', async () => {
|
|
85
|
-
const root = makeRepo({ git: false });
|
|
86
|
-
try {
|
|
87
|
-
let ran = false;
|
|
88
|
-
await withRepoLock(root, 'hub:seed', async () => {
|
|
89
|
-
ran = true;
|
|
90
|
-
});
|
|
91
|
-
expect(ran).toBe(true);
|
|
92
|
-
// It must not have become a repo initializer on the way past.
|
|
93
|
-
expect(() => readFileSync(join(root, '.git', REPO_LOCK_FILE))).toThrow();
|
|
94
|
-
} finally {
|
|
95
|
-
rmSync(root, { recursive: true, force: true });
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('a stale lock is stolen — a cell that crashed mid-commit unwedges itself', async () => {
|
|
100
|
-
const root = makeRepo();
|
|
101
|
-
try {
|
|
102
|
-
// A holder that still EXISTS as a process (this one) so the steal can only
|
|
103
|
-
// be attributed to the age, not to the liveness check.
|
|
104
|
-
writeFileSync(
|
|
105
|
-
lockFile(root),
|
|
106
|
-
JSON.stringify({ pid: process.pid, holder: 'hub:autocommit', at: 0, token: 'old' })
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
let ran = false;
|
|
110
|
-
const warned: string[] = [];
|
|
111
|
-
await withRepoLock(
|
|
112
|
-
root,
|
|
113
|
-
'studio:commit',
|
|
114
|
-
async () => {
|
|
115
|
-
ran = true;
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
waitMs: 1000,
|
|
119
|
-
// Age it past the window without waiting for it.
|
|
120
|
-
now: () => Date.now() + STALE_LOCK_MS + 5_000,
|
|
121
|
-
log: { warn: (m: string) => warned.push(m), log() {} },
|
|
122
|
-
}
|
|
123
|
-
);
|
|
124
|
-
expect(ran).toBe(true);
|
|
125
|
-
expect(warned.join(' ')).toContain('hub:autocommit');
|
|
126
|
-
} finally {
|
|
127
|
-
rmSync(root, { recursive: true, force: true });
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test('a lock whose process is gone is stolen without waiting out the window', async () => {
|
|
132
|
-
const root = makeRepo();
|
|
133
|
-
try {
|
|
134
|
-
// pid 1 is tini in a cell and always alive, so a pid that cannot exist is
|
|
135
|
-
// the honest fixture: 2^22 is above every default pid_max.
|
|
136
|
-
writeFileSync(
|
|
137
|
-
lockFile(root),
|
|
138
|
-
JSON.stringify({ pid: 4194304, holder: 'studio:checkout', at: Date.now(), token: 'dead' })
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
let ran = false;
|
|
142
|
-
await withRepoLock(
|
|
143
|
-
root,
|
|
144
|
-
'hub:autocommit',
|
|
145
|
-
async () => {
|
|
146
|
-
ran = true;
|
|
147
|
-
},
|
|
148
|
-
{ waitMs: 500, log: { warn() {}, log() {} } }
|
|
149
|
-
);
|
|
150
|
-
expect(ran).toBe(true);
|
|
151
|
-
} finally {
|
|
152
|
-
rmSync(root, { recursive: true, force: true });
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test('giving up names the holder rather than hanging', async () => {
|
|
157
|
-
const root = makeRepo();
|
|
158
|
-
try {
|
|
159
|
-
writeFileSync(
|
|
160
|
-
lockFile(root),
|
|
161
|
-
JSON.stringify({ pid: process.pid, holder: 'studio:fold', at: Date.now(), token: 'live' })
|
|
162
|
-
);
|
|
163
|
-
await expect(
|
|
164
|
-
withRepoLock(root, 'hub:autocommit', async () => undefined, { waitMs: 120 })
|
|
165
|
-
).rejects.toThrow(/studio:fold/);
|
|
166
|
-
} finally {
|
|
167
|
-
rmSync(root, { recursive: true, force: true });
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test('releasing never unlinks a lock that was stolen from us', async () => {
|
|
172
|
-
// The one way a lock can be worse than no lock: a slow holder finishes,
|
|
173
|
-
// unlinks, and pulls the lock out from under whoever legitimately took it
|
|
174
|
-
// after the steal.
|
|
175
|
-
const root = makeRepo();
|
|
176
|
-
try {
|
|
177
|
-
const finished = withRepoLock(root, 'hub:autocommit', async () => {
|
|
178
|
-
// Simulate the steal: somebody reaped us and took it.
|
|
179
|
-
writeFileSync(
|
|
180
|
-
lockFile(root),
|
|
181
|
-
JSON.stringify({
|
|
182
|
-
pid: process.pid,
|
|
183
|
-
holder: 'studio:checkout',
|
|
184
|
-
at: Date.now(),
|
|
185
|
-
token: 'theirs',
|
|
186
|
-
})
|
|
187
|
-
);
|
|
188
|
-
});
|
|
189
|
-
await finished;
|
|
190
|
-
const after = JSON.parse(readFileSync(lockFile(root), 'utf8'));
|
|
191
|
-
expect(after.holder).toBe('studio:checkout');
|
|
192
|
-
} finally {
|
|
193
|
-
rmSync(root, { recursive: true, force: true });
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
test("a lost steal race does not unlink the winner's fresh lock", async () => {
|
|
197
|
-
// THE MUTUAL-EXCLUSION FAILURE THIS LOCK EXISTS TO PREVENT, REINTRODUCED BY
|
|
198
|
-
// ITS OWN RECOVERY PATH. Two waiters both see holder A as stale; the first
|
|
199
|
-
// unlinks and acquires; the second — still acting on its read of A —
|
|
200
|
-
// unlinks the FIRST's fresh lock and acquires too. Both then run their
|
|
201
|
-
// critical section over one git index.
|
|
202
|
-
//
|
|
203
|
-
// The fixture is aged through its MTIME rather than through a fake clock,
|
|
204
|
-
// and that distinction is the test: a fake `now` makes every lock look
|
|
205
|
-
// ancient, including the winner's brand-new one, which is not a state the
|
|
206
|
-
// real world can produce and would assert something nobody needs.
|
|
207
|
-
const root = makeRepo();
|
|
208
|
-
try {
|
|
209
|
-
writeFileSync(
|
|
210
|
-
lockFile(root),
|
|
211
|
-
JSON.stringify({ pid: process.pid, holder: 'hub:autocommit', at: 0, token: 'stale' })
|
|
212
|
-
);
|
|
213
|
-
const long_ago = new Date(Date.now() - STALE_LOCK_MS * 4);
|
|
214
|
-
utimesSync(lockFile(root), long_ago, long_ago);
|
|
215
|
-
|
|
216
|
-
let concurrent = 0;
|
|
217
|
-
let maxConcurrent = 0;
|
|
218
|
-
const contend = (name: string) =>
|
|
219
|
-
withRepoLock(
|
|
220
|
-
root,
|
|
221
|
-
name,
|
|
222
|
-
async () => {
|
|
223
|
-
concurrent += 1;
|
|
224
|
-
maxConcurrent = Math.max(maxConcurrent, concurrent);
|
|
225
|
-
await sleep(60);
|
|
226
|
-
concurrent -= 1;
|
|
227
|
-
},
|
|
228
|
-
{ waitMs: 5000, log: { warn() {}, log() {} } }
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
await Promise.all([contend('one'), contend('two'), contend('three')]);
|
|
232
|
-
expect(maxConcurrent).toBe(1);
|
|
233
|
-
} finally {
|
|
234
|
-
rmSync(root, { recursive: true, force: true });
|
|
235
|
-
}
|
|
236
|
-
}, 20_000);
|
|
237
|
-
|
|
238
|
-
test('a long-but-live holder is not stolen from — the heartbeat says it is alive', async () => {
|
|
239
|
-
// `pull`, `fold` and `resolve` came under this lock in D2, and they do
|
|
240
|
-
// NETWORK I/O. Without a heartbeat the staleness test cannot tell "held for
|
|
241
|
-
// 30 s" from "the holder died 30 s ago", so the next waiter steals the lock
|
|
242
|
-
// out from under a running merge.
|
|
243
|
-
const root = makeRepo();
|
|
244
|
-
try {
|
|
245
|
-
const order: string[] = [];
|
|
246
|
-
const slow = withRepoLock(root, 'studio:pull', async () => {
|
|
247
|
-
order.push('slow:in');
|
|
248
|
-
await sleep(HEARTBEAT_MS + 400);
|
|
249
|
-
order.push('slow:out');
|
|
250
|
-
});
|
|
251
|
-
await sleep(50);
|
|
252
|
-
// A waiter whose clock is PAST the staleness window. The heartbeat has
|
|
253
|
-
// refreshed the mtime, so the lock is not stale by age and the holder is
|
|
254
|
-
// alive — it must wait, not steal.
|
|
255
|
-
const waiter = withRepoLock(
|
|
256
|
-
root,
|
|
257
|
-
'hub:autocommit',
|
|
258
|
-
async () => {
|
|
259
|
-
order.push('waiter:in');
|
|
260
|
-
},
|
|
261
|
-
{ waitMs: 20_000, log: { warn() {}, log() {} } }
|
|
262
|
-
);
|
|
263
|
-
await Promise.all([slow, waiter]);
|
|
264
|
-
expect(order).toEqual(['slow:in', 'slow:out', 'waiter:in']);
|
|
265
|
-
} finally {
|
|
266
|
-
rmSync(root, { recursive: true, force: true });
|
|
267
|
-
}
|
|
268
|
-
}, 30_000);
|
|
269
|
-
|
|
270
|
-
test('a hostile holder label cannot forge a log line', async () => {
|
|
271
|
-
const root = makeRepo();
|
|
272
|
-
try {
|
|
273
|
-
writeFileSync(
|
|
274
|
-
lockFile(root),
|
|
275
|
-
JSON.stringify({
|
|
276
|
-
pid: process.pid,
|
|
277
|
-
holder: 'evil\n[repo-lock] PASS: nothing to see here',
|
|
278
|
-
at: 0,
|
|
279
|
-
token: 'x',
|
|
280
|
-
})
|
|
281
|
-
);
|
|
282
|
-
const long_ago = new Date(Date.now() - STALE_LOCK_MS * 4);
|
|
283
|
-
utimesSync(lockFile(root), long_ago, long_ago);
|
|
284
|
-
const warned: string[] = [];
|
|
285
|
-
await withRepoLock(root, 'hub:autocommit', async () => undefined, {
|
|
286
|
-
waitMs: 2000,
|
|
287
|
-
log: { warn: (m: string) => warned.push(m), log() {} },
|
|
288
|
-
});
|
|
289
|
-
expect(warned.join(' ')).not.toContain('\n');
|
|
290
|
-
expect(warned.join(' ')).not.toContain('nothing to see here');
|
|
291
|
-
} finally {
|
|
292
|
-
rmSync(root, { recursive: true, force: true });
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
test('exactly ONE waiter can steal a dead lock — the steal is a compare-and-swap', async () => {
|
|
296
|
-
// The `unlink` this replaced could not say no: two waiters both judged the
|
|
297
|
-
// holder stale, the first unlinked and acquired, and the second unlinked
|
|
298
|
-
// the FIRST's fresh lock. `rename` fails with ENOENT when the source is
|
|
299
|
-
// already gone, so only one waiter moves the file aside and the rest go
|
|
300
|
-
// back around the loop.
|
|
301
|
-
//
|
|
302
|
-
// WHAT THIS TEST DOES AND DOES NOT PROVE, because the difference matters.
|
|
303
|
-
// It asserts the OBSERVABLE contract — one steal, no leftover sidecar,
|
|
304
|
-
// never two holders. It does NOT reproduce the interleaving that made the
|
|
305
|
-
// unlink unsafe: that race is between the hub PROCESS and the studio
|
|
306
|
-
// PROCESS, and inside one bun test the whole read → steal → acquire
|
|
307
|
-
// sequence runs synchronously, so a single-process harness reaches the same
|
|
308
|
-
// verdict with either implementation (measured, not assumed). The
|
|
309
|
-
// correctness argument for `rename` is its atomicity — exactly one caller
|
|
310
|
-
// can move a given file — and this test guards the contract around it.
|
|
311
|
-
const root = makeRepo();
|
|
312
|
-
try {
|
|
313
|
-
writeFileSync(
|
|
314
|
-
lockFile(root),
|
|
315
|
-
JSON.stringify({ pid: 4194304, holder: 'hub:autocommit', at: 0, token: 'dead' })
|
|
316
|
-
);
|
|
317
|
-
const long_ago = new Date(Date.now() - STALE_LOCK_MS * 4);
|
|
318
|
-
utimesSync(lockFile(root), long_ago, long_ago);
|
|
319
|
-
|
|
320
|
-
const steals: string[] = [];
|
|
321
|
-
let concurrent = 0;
|
|
322
|
-
let maxConcurrent = 0;
|
|
323
|
-
const contend = (name: string) =>
|
|
324
|
-
withRepoLock(
|
|
325
|
-
root,
|
|
326
|
-
name,
|
|
327
|
-
async () => {
|
|
328
|
-
concurrent += 1;
|
|
329
|
-
maxConcurrent = Math.max(maxConcurrent, concurrent);
|
|
330
|
-
await sleep(40);
|
|
331
|
-
concurrent -= 1;
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
waitMs: 5000,
|
|
335
|
-
log: { warn: (m: string) => steals.push(m), log() {} },
|
|
336
|
-
}
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
await Promise.all([contend('a'), contend('b'), contend('c'), contend('d')]);
|
|
340
|
-
|
|
341
|
-
expect(maxConcurrent).toBe(1);
|
|
342
|
-
// One steal, not four: the other three found the file already gone.
|
|
343
|
-
expect(steals.filter((m) => m.includes('stole a')).length).toBe(1);
|
|
344
|
-
// And no sidecar left behind.
|
|
345
|
-
expect(readdirSync(join(root, '.git')).filter((f) => f.includes('.stale-'))).toEqual([]);
|
|
346
|
-
} finally {
|
|
347
|
-
rmSync(root, { recursive: true, force: true });
|
|
348
|
-
}
|
|
349
|
-
}, 20_000);
|
|
350
|
-
});
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
// Regression test for issue-report-a-bug-http-500 (RCA:
|
|
2
|
-
// .ai/logs/rca/issue-report-a-bug-http-500.md). `/_api/report` proxies the
|
|
3
|
-
// dialog's multipart body to `MAUDE_REPORT_URL`. A bare `fetch(endpoint, …)`
|
|
4
|
-
// call inside http.ts silently resolved to this module's OWN same-named
|
|
5
|
-
// fall-through handler instead of the global `fetch` — the request never left
|
|
6
|
-
// the machine, and the route mirrored a bogus local 500 back to the client.
|
|
7
|
-
// These tests boot a real server pointed at a real stub upstream, so a
|
|
8
|
-
// reintroduced shadow (or any other break in the proxy hop) fails loudly
|
|
9
|
-
// instead of only being caught by a mocked-upstream unit test.
|
|
10
|
-
|
|
11
|
-
import { describe, expect, test } from 'bun:test';
|
|
12
|
-
import { readFileSync } from 'node:fs';
|
|
13
|
-
|
|
14
|
-
import { bootServer, killProc, makeSandbox, nextPort } from './_helpers.ts';
|
|
15
|
-
|
|
16
|
-
async function bootStubUpstream(
|
|
17
|
-
handler: (req: Request) => Response | Promise<Response>
|
|
18
|
-
): Promise<{ url: string; stop: () => void; received: () => number }> {
|
|
19
|
-
let received = 0;
|
|
20
|
-
const server = Bun.serve({
|
|
21
|
-
port: 0,
|
|
22
|
-
fetch: async (req) => {
|
|
23
|
-
received += 1;
|
|
24
|
-
return handler(req);
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
return {
|
|
28
|
-
url: `http://127.0.0.1:${server.port}`,
|
|
29
|
-
stop: () => server.stop(true),
|
|
30
|
-
received: () => received,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
describe('/_api/report proxy hop', () => {
|
|
35
|
-
test('forwards the multipart body verbatim and mirrors a successful upstream response', async () => {
|
|
36
|
-
const { root } = makeSandbox();
|
|
37
|
-
let capturedBytes = 0;
|
|
38
|
-
let capturedHasReportPart = false;
|
|
39
|
-
const upstream = await bootStubUpstream(async (req) => {
|
|
40
|
-
const form = await req.formData();
|
|
41
|
-
capturedHasReportPart = typeof form.get('report') === 'string';
|
|
42
|
-
// Multipart parsing consumed the body — approximate "did real bytes
|
|
43
|
-
// arrive" by re-serializing what FormData kept.
|
|
44
|
-
capturedBytes = [...form.values()].reduce(
|
|
45
|
-
(n, v) => n + (typeof v === 'string' ? v.length : (v as Blob).size),
|
|
46
|
-
0
|
|
47
|
-
);
|
|
48
|
-
return Response.json({ ok: true, issueNumber: 1, issueUrl: 'https://example.com/issues/1' });
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const port = nextPort();
|
|
52
|
-
const proc = await bootServer(root, port, { MAUDE_REPORT_URL: `${upstream.url}/report` });
|
|
53
|
-
try {
|
|
54
|
-
const form = new FormData();
|
|
55
|
-
form.set(
|
|
56
|
-
'report',
|
|
57
|
-
JSON.stringify({ schema: 'maude-report/v1', description: 'test', reportId: 'r-0123abcd' })
|
|
58
|
-
);
|
|
59
|
-
form.append('screenshot', new Blob([new Uint8Array([1, 2, 3, 4])]), 'shot.png');
|
|
60
|
-
|
|
61
|
-
const res = await fetch(`http://localhost:${port}/_api/report`, {
|
|
62
|
-
method: 'POST',
|
|
63
|
-
body: form,
|
|
64
|
-
});
|
|
65
|
-
expect(res.status).toBe(200);
|
|
66
|
-
const json = await res.json();
|
|
67
|
-
expect(json).toEqual({ ok: true, issueNumber: 1, issueUrl: 'https://example.com/issues/1' });
|
|
68
|
-
|
|
69
|
-
expect(upstream.received()).toBe(1);
|
|
70
|
-
expect(capturedHasReportPart).toBe(true);
|
|
71
|
-
expect(capturedBytes).toBeGreaterThan(0);
|
|
72
|
-
} finally {
|
|
73
|
-
await killProc(proc);
|
|
74
|
-
upstream.stop();
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('an unreachable upstream yields 502 with a JSON error, not a bare 500', async () => {
|
|
79
|
-
const { root } = makeSandbox();
|
|
80
|
-
const port = nextPort();
|
|
81
|
-
// Nothing listens on this port — the fetch itself must fail/timeout.
|
|
82
|
-
const proc = await bootServer(root, port, { MAUDE_REPORT_URL: 'http://127.0.0.1:1/report' });
|
|
83
|
-
try {
|
|
84
|
-
const form = new FormData();
|
|
85
|
-
form.set(
|
|
86
|
-
'report',
|
|
87
|
-
JSON.stringify({ schema: 'maude-report/v1', description: 'test', reportId: 'r-0123abcd' })
|
|
88
|
-
);
|
|
89
|
-
const res = await fetch(`http://localhost:${port}/_api/report`, {
|
|
90
|
-
method: 'POST',
|
|
91
|
-
body: form,
|
|
92
|
-
});
|
|
93
|
-
expect(res.status).toBe(502);
|
|
94
|
-
const json = await res.json();
|
|
95
|
-
expect(json).toEqual({ error: 'report service unreachable' });
|
|
96
|
-
} finally {
|
|
97
|
-
await killProc(proc);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
describe('no bare `fetch(` call sites in http.ts', () => {
|
|
103
|
-
test('every fetch call is either the global fetch (qualified) or a local handler name', () => {
|
|
104
|
-
const src = readFileSync(new URL('../http.ts', import.meta.url), 'utf8');
|
|
105
|
-
// A bare `fetch(` immediately preceded by a word-boundary and NOT preceded
|
|
106
|
-
// by a `.` (member access) or `function ` (declaration) would silently
|
|
107
|
-
// resolve to this file's own same-named fall-through handler instead of
|
|
108
|
-
// the global — exactly the bug this test guards against. `handleFallthrough`
|
|
109
|
-
// is the current, unambiguous name; this assertion also fails if the
|
|
110
|
-
// handler is ever renamed back to something call-site-ambiguous.
|
|
111
|
-
const bareFetchCalls = [...src.matchAll(/(?<![.\w])fetch\(/g)];
|
|
112
|
-
const lines = src.split('\n');
|
|
113
|
-
// Skip: comment lines (mentioning `fetch(` in prose is fine) and the one
|
|
114
|
-
// legitimate bare occurrence — the type-signature parameter name
|
|
115
|
-
// `fetch(req: Request): Promise<Response>;` in the `Api`-shaped interface,
|
|
116
|
-
// which is a declaration, not a call.
|
|
117
|
-
const offendingLines = bareFetchCalls
|
|
118
|
-
.map((m) => src.slice(0, m.index).split('\n').length)
|
|
119
|
-
.map((line) => ({ line, text: lines[line - 1] ?? '' }))
|
|
120
|
-
.filter(({ text }) => !/^\s*(\/\/|\*)/.test(text))
|
|
121
|
-
.filter(({ text }) => !/^\s*fetch\(req: Request\): Promise<Response>;/.test(text));
|
|
122
|
-
|
|
123
|
-
expect(offendingLines).toEqual([]);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
// ripple.test.ts — feature-enhanced-video-editing Phase 0 (Task 2). Golden-file
|
|
2
|
-
// fixtures for the pure ripple engine: literal shifts, const-arithmetic
|
|
3
|
-
// trailing-term merges, the TOTAL-follows-const double-shift guard, series
|
|
4
|
-
// TOTAL-only behavior, and the loud structured refusals.
|
|
5
|
-
|
|
6
|
-
import { describe, expect, test } from 'bun:test';
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
applyRippleAfterClip,
|
|
10
|
-
collectConsts,
|
|
11
|
-
RippleError,
|
|
12
|
-
resolveNum,
|
|
13
|
-
shiftExpression,
|
|
14
|
-
} from '../ripple.ts';
|
|
15
|
-
|
|
16
|
-
const CANVAS = '/abs/Canvas.tsx';
|
|
17
|
-
|
|
18
|
-
describe('shiftExpression', () => {
|
|
19
|
-
test('literal rewrites in place', () => {
|
|
20
|
-
expect(shiftExpression('90', 12)).toBe('102');
|
|
21
|
-
expect(shiftExpression('90', -30)).toBe('60');
|
|
22
|
-
});
|
|
23
|
-
test('trailing integer term merges', () => {
|
|
24
|
-
expect(shiftExpression('A + B - 20', 12)).toBe('A + B - 8');
|
|
25
|
-
expect(shiftExpression('A + B - 20', 20)).toBe('A + B');
|
|
26
|
-
expect(shiftExpression('A + 10', -22)).toBe('A - 12');
|
|
27
|
-
});
|
|
28
|
-
test('no trailing term → appends', () => {
|
|
29
|
-
expect(shiftExpression('A', 12)).toBe('A + 12');
|
|
30
|
-
expect(shiftExpression('A + B', -12)).toBe('A + B - 12');
|
|
31
|
-
});
|
|
32
|
-
test('trailing int that is an operand of * stays untouched (appends instead)', () => {
|
|
33
|
-
expect(shiftExpression('A * 2', 12)).toBe('A * 2 + 12');
|
|
34
|
-
});
|
|
35
|
-
test('unsafe charset → null (a call slips the charset but fails resolveNum — the engine refuses there)', () => {
|
|
36
|
-
expect(shiftExpression('a ? 1 : 2', 12)).toBe(null);
|
|
37
|
-
expect(resolveNum('fn(A)', { A: 1 })).toBe(null);
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe('collectConsts + resolveNum', () => {
|
|
42
|
-
const SRC = 'const A = 40;\nconst B = 50;\nconst TOTAL = A + B - 20;\n';
|
|
43
|
-
test('derived consts resolve', () => {
|
|
44
|
-
const c = collectConsts(SRC);
|
|
45
|
-
expect(c.TOTAL).toBe(70);
|
|
46
|
-
expect(resolveNum('TOTAL - 10', c)).toBe(60);
|
|
47
|
-
expect(resolveNum('mystery + 1', c)).toBe(null);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
// Standalone-Sequence comp (overlay/audio band shape) — literal + expression.
|
|
53
|
-
|
|
54
|
-
const STANDALONE = [
|
|
55
|
-
'const A = 90;',
|
|
56
|
-
'const TOTAL = 300;',
|
|
57
|
-
'const Comp = () => (',
|
|
58
|
-
' <>',
|
|
59
|
-
' <Sequence name="one" from={0} durationInFrames={A}><Video src="a.mp4" /></Sequence>',
|
|
60
|
-
' <Sequence name="two" from={90} durationInFrames={100}><Video src="b.mp4" /></Sequence>',
|
|
61
|
-
' <Sequence name="three" from={A + 100} durationInFrames={110}><Video src="c.mp4" /></Sequence>',
|
|
62
|
-
' </>',
|
|
63
|
-
');',
|
|
64
|
-
'function Canvas() {',
|
|
65
|
-
' return (',
|
|
66
|
-
' <DCArtboard id="a"><VideoComp component={Comp} durationInFrames={TOTAL} fps={30} /></DCArtboard>',
|
|
67
|
-
' );',
|
|
68
|
-
'}',
|
|
69
|
-
].join('\n');
|
|
70
|
-
|
|
71
|
-
describe('applyRippleAfterClip — standalone sequences', () => {
|
|
72
|
-
test('shifts literal and expression froms, bumps the TOTAL const', () => {
|
|
73
|
-
const r = applyRippleAfterClip(CANVAS, STANDALONE, 'a', 'name:one', 12);
|
|
74
|
-
expect(r.totalEdited).toBe(true);
|
|
75
|
-
expect(r.shifted).toEqual(['name:two', 'name:three']);
|
|
76
|
-
expect(r.source).toContain('const TOTAL = 312;');
|
|
77
|
-
expect(r.source).toContain('from={102}');
|
|
78
|
-
expect(r.source).toContain('from={A + 112}');
|
|
79
|
-
// upstream untouched
|
|
80
|
-
expect(r.source).toContain('from={0}');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('negative delta merges the trailing term back down', () => {
|
|
84
|
-
const r = applyRippleAfterClip(CANVAS, STANDALONE, 'a', 'name:one', -30);
|
|
85
|
-
expect(r.source).toContain('const TOTAL = 270;');
|
|
86
|
-
expect(r.source).toContain('from={60}');
|
|
87
|
-
expect(r.source).toContain('from={A + 70}');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('delta 0 is a no-op', () => {
|
|
91
|
-
const r = applyRippleAfterClip(CANVAS, STANDALONE, 'a', 'name:one', 0);
|
|
92
|
-
expect(r.source).toBe(STANDALONE);
|
|
93
|
-
expect(r.shifted).toEqual([]);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('only clips AFTER the target shift', () => {
|
|
97
|
-
const r = applyRippleAfterClip(CANVAS, STANDALONE, 'a', 'name:two', 12);
|
|
98
|
-
expect(r.shifted).toEqual(['name:three']);
|
|
99
|
-
expect(r.source).toContain('from={90}'); // "two" itself untouched
|
|
100
|
-
expect(r.source).toContain('from={A + 112}');
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// ---------------------------------------------------------------------------
|
|
105
|
-
// The double-shift guard — an end card at `from={TOTAL - 60}` already follows
|
|
106
|
-
// the bumped const and must be SKIPPED, not shifted twice.
|
|
107
|
-
|
|
108
|
-
const COUPLED = [
|
|
109
|
-
'const TOTAL = 300;',
|
|
110
|
-
'const Comp = () => (',
|
|
111
|
-
' <>',
|
|
112
|
-
' <Sequence name="body" from={0} durationInFrames={240}><Video src="a.mp4" /></Sequence>',
|
|
113
|
-
' <Sequence name="end" from={TOTAL - 60} durationInFrames={60}><Video src="b.mp4" /></Sequence>',
|
|
114
|
-
' </>',
|
|
115
|
-
');',
|
|
116
|
-
'function Canvas() {',
|
|
117
|
-
' return (',
|
|
118
|
-
' <DCArtboard id="a"><VideoComp component={Comp} durationInFrames={TOTAL} fps={30} /></DCArtboard>',
|
|
119
|
-
' );',
|
|
120
|
-
'}',
|
|
121
|
-
].join('\n');
|
|
122
|
-
|
|
123
|
-
describe('applyRippleAfterClip — const-coupled froms', () => {
|
|
124
|
-
test('a from that follows the bumped TOTAL is skipped (no double shift)', () => {
|
|
125
|
-
const r = applyRippleAfterClip(CANVAS, COUPLED, 'a', 'name:body', 30);
|
|
126
|
-
expect(r.source).toContain('const TOTAL = 330;');
|
|
127
|
-
expect(r.source).toContain('from={TOTAL - 60}'); // untouched — follows the const
|
|
128
|
-
expect(r.shifted).toEqual([]);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// ---------------------------------------------------------------------------
|
|
133
|
-
// TransitionSeries storyline — series clips get NO from; TOTAL still moves.
|
|
134
|
-
|
|
135
|
-
const SERIES = [
|
|
136
|
-
"import { TransitionSeries } from '@remotion/transitions';",
|
|
137
|
-
'const S1 = 90;',
|
|
138
|
-
'const S2 = 120;',
|
|
139
|
-
'const XF = 15;',
|
|
140
|
-
'const TOTAL = S1 + S2 - XF;',
|
|
141
|
-
'const Comp = () => (',
|
|
142
|
-
' <TransitionSeries>',
|
|
143
|
-
' <TransitionSeries.Sequence name="s1" durationInFrames={S1}><Video src="a.mp4" /></TransitionSeries.Sequence>',
|
|
144
|
-
' <TransitionSeries.Transition presentation={fade()} timing={linearTiming({ durationInFrames: XF })} />',
|
|
145
|
-
' <TransitionSeries.Sequence name="s2" durationInFrames={S2}><Video src="b.mp4" /></TransitionSeries.Sequence>',
|
|
146
|
-
' </TransitionSeries>',
|
|
147
|
-
');',
|
|
148
|
-
'function Canvas() {',
|
|
149
|
-
' return (',
|
|
150
|
-
' <DCArtboard id="a"><VideoComp component={Comp} durationInFrames={TOTAL} fps={30} /></DCArtboard>',
|
|
151
|
-
' );',
|
|
152
|
-
'}',
|
|
153
|
-
].join('\n');
|
|
154
|
-
|
|
155
|
-
describe('applyRippleAfterClip — series storyline', () => {
|
|
156
|
-
test('series clips get no from shift; TOTAL const merges the delta', () => {
|
|
157
|
-
const r = applyRippleAfterClip(CANVAS, SERIES, 'a', 'name:s1', 12);
|
|
158
|
-
expect(r.totalEdited).toBe(true);
|
|
159
|
-
expect(r.shifted).toEqual([]);
|
|
160
|
-
expect(r.source).toContain('const TOTAL = S1 + S2 - XF + 12;');
|
|
161
|
-
// clip tags untouched
|
|
162
|
-
expect(r.source).toContain('durationInFrames={S1}');
|
|
163
|
-
expect(r.source).toContain('durationInFrames={S2}');
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// ---------------------------------------------------------------------------
|
|
168
|
-
// Refusals — loud, structured, never a silent mis-shift.
|
|
169
|
-
|
|
170
|
-
describe('applyRippleAfterClip — refusals', () => {
|
|
171
|
-
test('unresolvable downstream from refuses with the clip named', () => {
|
|
172
|
-
const SRC = STANDALONE.replace('from={A + 100}', 'from={computeStart()}');
|
|
173
|
-
let err: unknown;
|
|
174
|
-
try {
|
|
175
|
-
applyRippleAfterClip(CANVAS, SRC, 'a', 'name:one', 12);
|
|
176
|
-
} catch (e) {
|
|
177
|
-
err = e;
|
|
178
|
-
}
|
|
179
|
-
expect(err).toBeInstanceOf(RippleError);
|
|
180
|
-
expect(String((err as Error).message)).toContain('name:three');
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test('unresolvable TOTAL const refuses', () => {
|
|
184
|
-
const SRC = STANDALONE.replace('const TOTAL = 300;', 'const TOTAL = computeTotal();');
|
|
185
|
-
expect(() => applyRippleAfterClip(CANVAS, SRC, 'a', 'name:one', 12)).toThrow(RippleError);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test('a shift below frame 0 refuses', () => {
|
|
189
|
-
expect(() => applyRippleAfterClip(CANVAS, STANDALONE, 'a', 'name:one', -120)).toThrow(
|
|
190
|
-
RippleError
|
|
191
|
-
);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test('unknown clip refuses', () => {
|
|
195
|
-
expect(() => applyRippleAfterClip(CANVAS, STANDALONE, 'a', 'name:nope', 12)).toThrow();
|
|
196
|
-
});
|
|
197
|
-
});
|