@1agh/maude 0.42.0 → 0.44.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/.ai/cache/_stats.json +7 -0
- package/apps/studio/acp/bootstrap-brief.ts +12 -2
- package/apps/studio/acp/bridge.ts +44 -10
- package/apps/studio/acp/env.ts +23 -0
- package/apps/studio/acp/login-state.ts +263 -0
- package/apps/studio/acp/plugin-bootstrap.ts +31 -25
- package/apps/studio/acp/probe.ts +152 -1
- package/apps/studio/annotations-layer.tsx +1451 -324
- package/apps/studio/annotations-model.ts +58 -0
- package/apps/studio/api.ts +568 -24
- package/apps/studio/bin/_audio-search.mjs +140 -0
- package/apps/studio/bin/_import-asset-pdf-worker.mjs +34 -0
- package/apps/studio/bin/_import-asset.mjs +815 -0
- package/apps/studio/bin/_import-brand.mjs +635 -0
- package/apps/studio/bin/_import-tokens-alias-resolver.mjs +95 -0
- package/apps/studio/bin/_import-tokens.mjs +1201 -0
- package/apps/studio/bin/_ingest-footage.mjs +386 -0
- package/apps/studio/bin/_probe-footage-playwright.mjs +269 -0
- package/apps/studio/bin/_transcribe.mjs +419 -0
- package/apps/studio/bin/_transcribe.test.mjs +80 -0
- package/apps/studio/bin/_video-playwright.mjs +141 -71
- package/apps/studio/bin/audio-search.sh +28 -0
- package/apps/studio/bin/generate.sh +154 -0
- package/apps/studio/bin/import-asset.sh +34 -0
- package/apps/studio/bin/import-brand.sh +29 -0
- package/apps/studio/bin/import-tokens.sh +33 -0
- package/apps/studio/bin/ingest-footage.sh +27 -0
- package/apps/studio/bin/photo-adjust.sh +163 -0
- package/apps/studio/bin/photo-bg-remove.sh +258 -0
- package/apps/studio/bin/probe-footage.sh +28 -0
- package/apps/studio/bin/read-annotations.mjs +125 -2
- package/apps/studio/bin/transcribe.sh +39 -0
- package/apps/studio/canvas-edit.ts +656 -12
- package/apps/studio/canvas-lib.tsx +637 -10
- package/apps/studio/canvas-pipeline.ts +65 -7
- package/apps/studio/canvas-shell.tsx +156 -16
- package/apps/studio/client/app.jsx +2385 -699
- package/apps/studio/client/export-center.jsx +35 -1
- package/apps/studio/client/generate-dialog.jsx +352 -0
- package/apps/studio/client/github.js +18 -0
- package/apps/studio/client/inspector-controls.jsx +781 -0
- package/apps/studio/client/panels/BrandUploadPanel.jsx +233 -0
- package/apps/studio/client/panels/ChatPanel.jsx +15 -33
- package/apps/studio/client/panels/GitPanel.jsx +8 -0
- package/apps/studio/client/panels/IntroVideoDialog.jsx +44 -0
- package/apps/studio/client/panels/OnboardingWizard.jsx +12 -0
- package/apps/studio/client/panels/ReadinessList.jsx +227 -7
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +133 -21
- package/apps/studio/client/panels/SettingsPanel.jsx +828 -0
- package/apps/studio/client/panels/SetupChecklist.jsx +223 -0
- package/apps/studio/client/panels/StickerPicker.jsx +160 -0
- package/apps/studio/client/panels/TimelinePanel.jsx +15 -2
- package/apps/studio/client/photo-knobs.jsx +432 -0
- package/apps/studio/client/styles/3-shell-maude.css +376 -28
- package/apps/studio/client/styles/4-components.css +223 -0
- package/apps/studio/client/styles/6-acp-chat.css +86 -0
- package/apps/studio/client/tour/quick-setup-tour.js +39 -0
- package/apps/studio/commands/annotation-strokes-command.ts +13 -3
- package/apps/studio/commands/edit-source-command.ts +31 -4
- package/apps/studio/config.schema.json +70 -0
- package/apps/studio/context-menu.tsx +42 -8
- package/apps/studio/context.ts +16 -0
- package/apps/studio/design-setup-readiness.ts +115 -0
- package/apps/studio/dist/client.bundle.js +5572 -21
- package/apps/studio/dist/comment-mount.js +2 -2
- package/apps/studio/dist/runtime/.min-sizes.json +1 -0
- package/apps/studio/dist/runtime/@imgly_background-removal.js +5543 -0
- package/apps/studio/dist/runtime/pixi-js.js +519 -519
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/dom-selection.ts +25 -0
- package/apps/studio/draw/palette.ts +34 -0
- package/apps/studio/exporters/jobs.ts +47 -5
- package/apps/studio/exporters/video.ts +24 -4
- package/apps/studio/footage/schema.test.ts +287 -0
- package/apps/studio/footage/schema.ts +662 -0
- package/apps/studio/footage-store.ts +235 -0
- package/apps/studio/fs-watch.ts +10 -0
- package/apps/studio/generation/adapters/elevenlabs.ts +370 -0
- package/apps/studio/generation/adapters/gemini.ts +542 -0
- package/apps/studio/generation/adapters/groq.test.ts +80 -0
- package/apps/studio/generation/adapters/groq.ts +247 -0
- package/apps/studio/generation/audio-library.test.ts +95 -0
- package/apps/studio/generation/audio-library.ts +156 -0
- package/apps/studio/generation/captions.test.ts +78 -0
- package/apps/studio/generation/captions.ts +132 -0
- package/apps/studio/generation/download.ts +76 -0
- package/apps/studio/generation/elevenlabs.test.ts +223 -0
- package/apps/studio/generation/gemini.test.ts +350 -0
- package/apps/studio/generation/jobs.test.ts +128 -0
- package/apps/studio/generation/jobs.ts +243 -0
- package/apps/studio/generation/keys.test.ts +75 -0
- package/apps/studio/generation/keys.ts +162 -0
- package/apps/studio/generation/prefs.test.ts +95 -0
- package/apps/studio/generation/prefs.ts +82 -0
- package/apps/studio/generation/registry.ts +88 -0
- package/apps/studio/generation/types.test.ts +123 -0
- package/apps/studio/generation/types.ts +266 -0
- package/apps/studio/generation/whisper-models.test.ts +152 -0
- package/apps/studio/generation/whisper-models.ts +257 -0
- package/apps/studio/generation/whisper-spike-results.md +61 -0
- package/apps/studio/git/endpoints.ts +68 -7
- package/apps/studio/git/service.ts +199 -21
- package/apps/studio/github/service.ts +67 -0
- package/apps/studio/handoff.ts +3 -2
- package/apps/studio/http.ts +1039 -22
- package/apps/studio/input-router.tsx +10 -0
- package/apps/studio/inspect.ts +53 -1
- package/apps/studio/media/intro.mp4 +0 -0
- package/apps/studio/media-commit-chain.ts +119 -0
- package/apps/studio/paths.ts +20 -0
- package/apps/studio/photo/filters.ts +285 -0
- package/apps/studio/photo/pipeline.ts +607 -0
- package/apps/studio/photo/schema.ts +367 -0
- package/apps/studio/photo-store.ts +154 -0
- package/apps/studio/readiness.ts +108 -20
- package/apps/studio/runtime-bundle.ts +29 -6
- package/apps/studio/scaffold-design.ts +1145 -0
- package/apps/studio/server.ts +18 -2
- package/apps/studio/stickers/figjam-doodle/manifest.json +104 -0
- package/apps/studio/stickers/figjam-doodle/slice1.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice10.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice11.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice12.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice13.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice14.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice15.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice16.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice17.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice18.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice19.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice2.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice20.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice21.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice22.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice23.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice24.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice3.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice4.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice5.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice6.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice7.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice8.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice9.png +0 -0
- package/apps/studio/stickers/life-style/best.png +0 -0
- package/apps/studio/stickers/life-style/come-on.png +0 -0
- package/apps/studio/stickers/life-style/cut.png +0 -0
- package/apps/studio/stickers/life-style/detox.png +0 -0
- package/apps/studio/stickers/life-style/just.png +0 -0
- package/apps/studio/stickers/life-style/manifest.json +68 -0
- package/apps/studio/stickers/life-style/mirror.png +0 -0
- package/apps/studio/stickers/life-style/nice.png +0 -0
- package/apps/studio/stickers/life-style/objects.png +0 -0
- package/apps/studio/stickers/life-style/ok.png +0 -0
- package/apps/studio/stickers/life-style/queen.png +0 -0
- package/apps/studio/stickers/life-style/star.png +0 -0
- package/apps/studio/stickers/life-style/sun.png +0 -0
- package/apps/studio/stickers/life-style/water.png +0 -0
- package/apps/studio/stickers/life-style/yeah.png +0 -0
- package/apps/studio/stickers/life-style/you-can.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/check-the-deets.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/cut-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/dope.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/fresh.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/hot.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/idea.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/keep-exploring.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/killed-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/love-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/manifest.json +88 -0
- package/apps/studio/stickers/opposing-thoughts/not-sure.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/ok.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/pure-gold.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/save-for-later.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/steal-this.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/take-a-peek.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/this-or-that.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/thoughts.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/vibes.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/winner.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/wip.png +0 -0
- package/apps/studio/stickers/project-status/group-100.png +0 -0
- package/apps/studio/stickers/project-status/group-101.png +0 -0
- package/apps/studio/stickers/project-status/group-102.png +0 -0
- package/apps/studio/stickers/project-status/group-103.png +0 -0
- package/apps/studio/stickers/project-status/group-104.png +0 -0
- package/apps/studio/stickers/project-status/group-105.png +0 -0
- package/apps/studio/stickers/project-status/group-106.png +0 -0
- package/apps/studio/stickers/project-status/group-107.png +0 -0
- package/apps/studio/stickers/project-status/group-108.png +0 -0
- package/apps/studio/stickers/project-status/group-109.png +0 -0
- package/apps/studio/stickers/project-status/group-110.png +0 -0
- package/apps/studio/stickers/project-status/group-111.png +0 -0
- package/apps/studio/stickers/project-status/group-112.png +0 -0
- package/apps/studio/stickers/project-status/group-113.png +0 -0
- package/apps/studio/stickers/project-status/group-114.png +0 -0
- package/apps/studio/stickers/project-status/group-115.png +0 -0
- package/apps/studio/stickers/project-status/group-117.png +0 -0
- package/apps/studio/stickers/project-status/group-118.png +0 -0
- package/apps/studio/stickers/project-status/group-119.png +0 -0
- package/apps/studio/stickers/project-status/group-120.png +0 -0
- package/apps/studio/stickers/project-status/group-121.png +0 -0
- package/apps/studio/stickers/project-status/group-122.png +0 -0
- package/apps/studio/stickers/project-status/group-41.png +0 -0
- package/apps/studio/stickers/project-status/group-42.png +0 -0
- package/apps/studio/stickers/project-status/group-43.png +0 -0
- package/apps/studio/stickers/project-status/group-44.png +0 -0
- package/apps/studio/stickers/project-status/group-45.png +0 -0
- package/apps/studio/stickers/project-status/group-46.png +0 -0
- package/apps/studio/stickers/project-status/group-47.png +0 -0
- package/apps/studio/stickers/project-status/group-48.png +0 -0
- package/apps/studio/stickers/project-status/group-49.png +0 -0
- package/apps/studio/stickers/project-status/group-50.png +0 -0
- package/apps/studio/stickers/project-status/group-51.png +0 -0
- package/apps/studio/stickers/project-status/group-52.png +0 -0
- package/apps/studio/stickers/project-status/group-53.png +0 -0
- package/apps/studio/stickers/project-status/group-54.png +0 -0
- package/apps/studio/stickers/project-status/group-55.png +0 -0
- package/apps/studio/stickers/project-status/group-56.png +0 -0
- package/apps/studio/stickers/project-status/group-57.png +0 -0
- package/apps/studio/stickers/project-status/group-58.png +0 -0
- package/apps/studio/stickers/project-status/group-59.png +0 -0
- package/apps/studio/stickers/project-status/group-60.png +0 -0
- package/apps/studio/stickers/project-status/group-61.png +0 -0
- package/apps/studio/stickers/project-status/group-62.png +0 -0
- package/apps/studio/stickers/project-status/group-63.png +0 -0
- package/apps/studio/stickers/project-status/group-64.png +0 -0
- package/apps/studio/stickers/project-status/group-65.png +0 -0
- package/apps/studio/stickers/project-status/group-66.png +0 -0
- package/apps/studio/stickers/project-status/group-67.png +0 -0
- package/apps/studio/stickers/project-status/group-68.png +0 -0
- package/apps/studio/stickers/project-status/group-69.png +0 -0
- package/apps/studio/stickers/project-status/group-70.png +0 -0
- package/apps/studio/stickers/project-status/group-71.png +0 -0
- package/apps/studio/stickers/project-status/group-72.png +0 -0
- package/apps/studio/stickers/project-status/group-73.png +0 -0
- package/apps/studio/stickers/project-status/group-74.png +0 -0
- package/apps/studio/stickers/project-status/group-75.png +0 -0
- package/apps/studio/stickers/project-status/group-76.png +0 -0
- package/apps/studio/stickers/project-status/group-77.png +0 -0
- package/apps/studio/stickers/project-status/group-78.png +0 -0
- package/apps/studio/stickers/project-status/group-79.png +0 -0
- package/apps/studio/stickers/project-status/group-80.png +0 -0
- package/apps/studio/stickers/project-status/group-81.png +0 -0
- package/apps/studio/stickers/project-status/group-82.png +0 -0
- package/apps/studio/stickers/project-status/group-83.png +0 -0
- package/apps/studio/stickers/project-status/group-84.png +0 -0
- package/apps/studio/stickers/project-status/group-85.png +0 -0
- package/apps/studio/stickers/project-status/group-86.png +0 -0
- package/apps/studio/stickers/project-status/group-87.png +0 -0
- package/apps/studio/stickers/project-status/group-88.png +0 -0
- package/apps/studio/stickers/project-status/group-89.png +0 -0
- package/apps/studio/stickers/project-status/group-90.png +0 -0
- package/apps/studio/stickers/project-status/group-91.png +0 -0
- package/apps/studio/stickers/project-status/group-92.png +0 -0
- package/apps/studio/stickers/project-status/group-93.png +0 -0
- package/apps/studio/stickers/project-status/group-94.png +0 -0
- package/apps/studio/stickers/project-status/group-96.png +0 -0
- package/apps/studio/stickers/project-status/group-97.png +0 -0
- package/apps/studio/stickers/project-status/group-98.png +0 -0
- package/apps/studio/stickers/project-status/group-99.png +0 -0
- package/apps/studio/stickers/project-status/manifest.json +328 -0
- package/apps/studio/test/_helpers.ts +15 -2
- package/apps/studio/test/acp-bootstrap-brief.test.ts +11 -0
- package/apps/studio/test/acp-bridge.test.ts +34 -1
- package/apps/studio/test/acp-commands.test.ts +2 -1
- package/apps/studio/test/acp-env.test.ts +30 -0
- package/apps/studio/test/acp-plugin-bootstrap.test.ts +23 -57
- package/apps/studio/test/acp-session-plugins.test.ts +72 -1
- package/apps/studio/test/annotation-strokes-command.test.ts +17 -0
- package/apps/studio/test/annotations-layer.test.ts +174 -0
- package/apps/studio/test/asset-api.test.ts +3 -3
- package/apps/studio/test/canvas-edit.test.ts +17 -6
- package/apps/studio/test/canvas-origin-gate.test.ts +50 -0
- package/apps/studio/test/csp-canvas-shell.test.ts +11 -0
- package/apps/studio/test/csrf-write-guard.test.ts +23 -1
- package/apps/studio/test/design-setup-readiness.test.ts +155 -0
- package/apps/studio/test/dynamic-text-edit.test.ts +162 -0
- package/apps/studio/test/edit-source-command.test.ts +23 -0
- package/apps/studio/test/element-structural-edit.test.ts +154 -0
- package/apps/studio/test/fixtures/fake-claude-auth.mjs +13 -0
- package/apps/studio/test/footage-store.test.ts +100 -0
- package/apps/studio/test/generate-route.test.ts +106 -0
- package/apps/studio/test/git-branches.test.ts +97 -0
- package/apps/studio/test/github-api.test.ts +56 -1
- package/apps/studio/test/import-asset-browser.test.ts +64 -0
- package/apps/studio/test/import-asset-route.test.ts +71 -0
- package/apps/studio/test/import-asset.test.ts +399 -0
- package/apps/studio/test/import-brand-browser.test.ts +88 -0
- package/apps/studio/test/import-brand-route.test.ts +120 -0
- package/apps/studio/test/import-brand.test.ts +206 -0
- package/apps/studio/test/import-tokens.test.ts +722 -0
- package/apps/studio/test/input-router.test.ts +33 -0
- package/apps/studio/test/inspect-script-syntax.test.ts +53 -0
- package/apps/studio/test/media-commit-chain.test.ts +210 -0
- package/apps/studio/test/photo-bg-remove-validation.test.ts +71 -0
- package/apps/studio/test/photo-canvas-bundle.test.ts +61 -0
- package/apps/studio/test/photo-edit-api.test.ts +201 -0
- package/apps/studio/test/photo-filters.test.ts +149 -0
- package/apps/studio/test/photo-pipeline.test.ts +106 -0
- package/apps/studio/test/photo-taxonomy.test.ts +96 -0
- package/apps/studio/test/read-annotations.test.ts +164 -0
- package/apps/studio/test/readiness.test.ts +13 -11
- package/apps/studio/test/scaffold-design.test.ts +122 -0
- package/apps/studio/test/shell-importmap.test.ts +49 -0
- package/apps/studio/test/text-editability-stamp.test.ts +131 -0
- package/apps/studio/test/timeline-parse.test.ts +57 -0
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +5 -3
- package/apps/studio/test/tour-overlay.test.tsx +16 -0
- package/apps/studio/test/undo-stack.test.ts +33 -0
- package/apps/studio/test/video-asset.test.ts +5 -5
- package/apps/studio/test/video-render-bridge.test.ts +23 -1
- package/apps/studio/text-caret.ts +239 -0
- package/apps/studio/tool-palette.tsx +49 -21
- package/apps/studio/ui-prefs.ts +130 -0
- package/apps/studio/undo-stack.ts +94 -2
- package/apps/studio/use-annotation-resize.tsx +4 -4
- package/apps/studio/use-canvas-media-drop.tsx +40 -1
- package/apps/studio/use-chrome-visibility.tsx +8 -2
- package/apps/studio/use-selection-set.tsx +21 -0
- package/apps/studio/video-comp.tsx +48 -7
- package/apps/studio/whats-new.json +149 -0
- package/apps/studio/ws.ts +5 -0
- package/cli/bin/maude.mjs +6 -6
- package/cli/commands/design.mjs +67 -3
- package/cli/lib/gitignore-block.mjs +2 -0
- package/cli/lib/pkg-root.mjs +107 -0
- package/cli/lib/pkg-root.test.mjs +79 -0
- package/cli/lib/reconstruct-toolset.test.mjs +194 -0
- package/cli/lib/update-check.mjs +8 -0
- package/package.json +13 -11
- package/plugins/design/dependencies.json +18 -0
- package/plugins/design/templates/_shell.html +1 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { scaffoldDesign } from '../scaffold-design.ts';
|
|
6
|
+
|
|
7
|
+
// DDR-166 Phase 1 — scaffoldDesign() now seeds THREE canvases (Welcome +
|
|
8
|
+
// the two onboarding reference canvases), embedded as escaped template
|
|
9
|
+
// literals. A bug in that escaping (an unescaped backtick/${ from the
|
|
10
|
+
// source .design/ui/*.tsx files) would silently truncate the written file
|
|
11
|
+
// into broken JS at scaffold time — every future new project would boot to
|
|
12
|
+
// a corrupt canvas. Exercise the REAL function against a REAL temp dir, not
|
|
13
|
+
// a mock, so a truncation shows up as a parse failure below.
|
|
14
|
+
describe('scaffoldDesign — onboarding canvas seeding', () => {
|
|
15
|
+
function scaffold() {
|
|
16
|
+
const dir = mkdtempSync(join(tmpdir(), 'maude-scaffold-test-'));
|
|
17
|
+
const result = scaffoldDesign(dir, 'Scaffold Test');
|
|
18
|
+
return { dir, result };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test('writes ok:true and all three seeded canvases + their meta sidecars', () => {
|
|
22
|
+
const { dir, result } = scaffold();
|
|
23
|
+
try {
|
|
24
|
+
expect(result.ok).toBe(true);
|
|
25
|
+
for (const name of ['Welcome', 'How to use Maude', 'How to make video']) {
|
|
26
|
+
expect(existsSync(join(dir, '.design', 'ui', `${name}.tsx`))).toBe(true);
|
|
27
|
+
expect(existsSync(join(dir, '.design', 'ui', `${name}.meta.json`))).toBe(true);
|
|
28
|
+
}
|
|
29
|
+
} finally {
|
|
30
|
+
rmSync(dir, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('the two onboarding canvases are non-trivially sized (catches a silent truncation)', () => {
|
|
35
|
+
const { dir } = scaffold();
|
|
36
|
+
try {
|
|
37
|
+
const useMaude = readFileSync(join(dir, '.design', 'ui', 'How to use Maude.tsx'), 'utf8');
|
|
38
|
+
const makeVideo = readFileSync(join(dir, '.design', 'ui', 'How to make video.tsx'), 'utf8');
|
|
39
|
+
// Real sizes are ~30KB/~24KB; a truncated-at-the-first-unescaped-backtick
|
|
40
|
+
// bug would cut this down to a few hundred bytes at most.
|
|
41
|
+
expect(useMaude.length).toBeGreaterThan(10_000);
|
|
42
|
+
expect(makeVideo.length).toBeGreaterThan(10_000);
|
|
43
|
+
// Both files must close their own template structure — the default
|
|
44
|
+
// export is the last thing in each source file.
|
|
45
|
+
expect(useMaude.trim().endsWith('}')).toBe(true);
|
|
46
|
+
expect(makeVideo.trim().endsWith('}')).toBe(true);
|
|
47
|
+
} finally {
|
|
48
|
+
rmSync(dir, { recursive: true, force: true });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('every seeded .tsx parses as valid TSX (Bun.Transpiler) and has exactly one default export', () => {
|
|
53
|
+
const { dir } = scaffold();
|
|
54
|
+
try {
|
|
55
|
+
const transpiler = new Bun.Transpiler({ loader: 'tsx' });
|
|
56
|
+
for (const name of ['Welcome', 'How to use Maude', 'How to make video']) {
|
|
57
|
+
const src = readFileSync(join(dir, '.design', 'ui', `${name}.tsx`), 'utf8');
|
|
58
|
+
expect(() => transpiler.transformSync(src)).not.toThrow();
|
|
59
|
+
expect(src).toMatch(/export default function \w+/);
|
|
60
|
+
}
|
|
61
|
+
} finally {
|
|
62
|
+
rmSync(dir, { recursive: true, force: true });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('every seeded meta.json is valid JSON with a matching artboards list', () => {
|
|
67
|
+
const { dir } = scaffold();
|
|
68
|
+
try {
|
|
69
|
+
// Welcome's meta nests artboards under sections[0] (the older, still-live
|
|
70
|
+
// convention); the two onboarding canvases use a top-level `artboards`.
|
|
71
|
+
const welcome = JSON.parse(
|
|
72
|
+
readFileSync(join(dir, '.design', 'ui', 'Welcome.meta.json'), 'utf8')
|
|
73
|
+
);
|
|
74
|
+
expect(welcome.sections[0].artboards.length).toBe(1);
|
|
75
|
+
for (const [name, count] of [
|
|
76
|
+
['How to use Maude', 10],
|
|
77
|
+
['How to make video', 7],
|
|
78
|
+
] as const) {
|
|
79
|
+
const meta = JSON.parse(
|
|
80
|
+
readFileSync(join(dir, '.design', 'ui', `${name}.meta.json`), 'utf8')
|
|
81
|
+
);
|
|
82
|
+
expect(Array.isArray(meta.artboards)).toBe(true);
|
|
83
|
+
expect(meta.artboards.length).toBe(count);
|
|
84
|
+
}
|
|
85
|
+
} finally {
|
|
86
|
+
rmSync(dir, { recursive: true, force: true });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('the two onboarding canvases self-declare their own tokens — no system/maude/ import', () => {
|
|
91
|
+
// Real bug caught while authoring this feature: the canvases originally
|
|
92
|
+
// imported system/maude/colors_and_type.css + preview/_components.css,
|
|
93
|
+
// which do NOT exist on a brand-new project (system/ is empty until
|
|
94
|
+
// /design:setup-ds runs) — every var(--token) would resolve to nothing,
|
|
95
|
+
// silently rendering unstyled black-on-black. Assert the fix holds.
|
|
96
|
+
const { dir } = scaffold();
|
|
97
|
+
try {
|
|
98
|
+
for (const name of ['How to use Maude', 'How to make video']) {
|
|
99
|
+
const src = readFileSync(join(dir, '.design', 'ui', `${name}.tsx`), 'utf8');
|
|
100
|
+
// Check for the actual import STATEMENT, not just the path appearing
|
|
101
|
+
// in an explanatory doc comment (which legitimately mentions it).
|
|
102
|
+
expect(src).not.toContain('import "../system/maude');
|
|
103
|
+
// The self-contained token block must still be present.
|
|
104
|
+
expect(src).toContain('const TOKENS');
|
|
105
|
+
expect(src).toContain('--bg-0');
|
|
106
|
+
}
|
|
107
|
+
} finally {
|
|
108
|
+
rmSync(dir, { recursive: true, force: true });
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('refuses to clobber an existing project (existing behavior, unchanged)', () => {
|
|
113
|
+
const { dir } = scaffold();
|
|
114
|
+
try {
|
|
115
|
+
const second = scaffoldDesign(dir, 'Scaffold Test');
|
|
116
|
+
expect(second.ok).toBe(false);
|
|
117
|
+
expect(second.error).toMatch(/already a Maude project/i);
|
|
118
|
+
} finally {
|
|
119
|
+
rmSync(dir, { recursive: true, force: true });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// shell-importmap.test.ts — regression guard for the bug that broke
|
|
2
|
+
// PhotoBgRemoveHarness's first real run (feature-photo-editor, Task 18):
|
|
3
|
+
// `@imgly/background-removal` was added to `RUNTIME_PACKAGES` (runtime-bundle.ts,
|
|
4
|
+
// Task 7/11) and its `/_canvas-runtime/@imgly_background-removal.js` bundle was
|
|
5
|
+
// built + floor-checked, but the hand-maintained `<script type="importmap">` in
|
|
6
|
+
// `_shell.html` never got the matching entry — so any CANVAS-side code
|
|
7
|
+
// (`import('@imgly/background-removal')`, only ever exercised by the shell's
|
|
8
|
+
// OWN client bundle until this task) hit a browser-native "Failed to resolve
|
|
9
|
+
// module specifier" the moment it actually ran inside a canvas iframe.
|
|
10
|
+
//
|
|
11
|
+
// The invariant: every entry in RUNTIME_PACKAGES must have a matching
|
|
12
|
+
// `_shell.html` importmap key pointing at `/_canvas-runtime/<slugFor(pkg)>.js`.
|
|
13
|
+
// canvas-build.ts externalises the WHOLE list unconditionally (`externalSpecifiers
|
|
14
|
+
// = new Set(RUNTIME_PACKAGES)`), so a package added there without a matching
|
|
15
|
+
// importmap entry silently ships a canvas-side dead import until something
|
|
16
|
+
// actually calls it at runtime — this test catches it at build time instead.
|
|
17
|
+
|
|
18
|
+
import { describe, expect, test } from 'bun:test';
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
|
|
22
|
+
import { DEV_SERVER_ROOT } from '../paths.ts';
|
|
23
|
+
import { RUNTIME_PACKAGES, slugFor } from '../runtime-bundle.ts';
|
|
24
|
+
|
|
25
|
+
const SHELL_HTML_PATH = join(
|
|
26
|
+
DEV_SERVER_ROOT,
|
|
27
|
+
'..',
|
|
28
|
+
'..',
|
|
29
|
+
'plugins',
|
|
30
|
+
'design',
|
|
31
|
+
'templates',
|
|
32
|
+
'_shell.html'
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
describe('_shell.html importmap agrees with RUNTIME_PACKAGES', () => {
|
|
36
|
+
const shellHtml = readFileSync(SHELL_HTML_PATH, 'utf8');
|
|
37
|
+
const importmapMatch = shellHtml.match(/<script type="importmap">([\s\S]*?)<\/script>/);
|
|
38
|
+
test('the importmap script block exists', () => {
|
|
39
|
+
expect(importmapMatch).not.toBeNull();
|
|
40
|
+
});
|
|
41
|
+
const importmap = JSON.parse(importmapMatch?.[1] ?? '{}') as { imports?: Record<string, string> };
|
|
42
|
+
const imports = importmap.imports ?? {};
|
|
43
|
+
|
|
44
|
+
for (const pkg of RUNTIME_PACKAGES) {
|
|
45
|
+
test(`"${pkg}" resolves to its /_canvas-runtime/ bundle`, () => {
|
|
46
|
+
expect(imports[pkg]).toBe(`/_canvas-runtime/${slugFor(pkg)}.js`);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { applyTextEdit } from '../canvas-edit.ts';
|
|
3
|
+
import { transpileCanvasSource } from '../canvas-pipeline.ts';
|
|
4
|
+
|
|
5
|
+
// Phase 6 (unified-text-editing) — the build-time `data-cd-editable="text"`
|
|
6
|
+
// marker must agree EXACTLY with applyTextEdit's acceptance (canvas-edit.ts):
|
|
7
|
+
// stamped ⇔ a commit would save. See isInlineEditableText in
|
|
8
|
+
// canvas-pipeline.ts (the mirrored predicate).
|
|
9
|
+
|
|
10
|
+
const FIXTURE = `export default function Fixture() {
|
|
11
|
+
return (
|
|
12
|
+
<div>
|
|
13
|
+
<h1>Maude desktop E2E</h1>
|
|
14
|
+
<p>
|
|
15
|
+
Deterministic, static canvas — proves the native shell booted the
|
|
16
|
+
sidecar, navigated the webview, and rendered a canvas.
|
|
17
|
+
</p>
|
|
18
|
+
<p>Total: {1 + 1} items</p>
|
|
19
|
+
<span>{title}</span>
|
|
20
|
+
<h2>{'Quoted title'}</h2>
|
|
21
|
+
<button>
|
|
22
|
+
Save
|
|
23
|
+
</button>
|
|
24
|
+
<section>
|
|
25
|
+
<em>nested</em>
|
|
26
|
+
</section>
|
|
27
|
+
<div />
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const title = 'x';
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
function stamped(withIds: string, tag: string): boolean {
|
|
35
|
+
const m = withIds.match(new RegExp(`<${tag}[^>]*>`));
|
|
36
|
+
if (!m) throw new Error(`tag <${tag}> not found in output`);
|
|
37
|
+
return m[0].includes('data-cd-editable="text"');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe('data-cd-editable build-time stamp', () => {
|
|
41
|
+
const { withIds } = transpileCanvasSource('/virtual/Fixture.tsx', FIXTURE);
|
|
42
|
+
|
|
43
|
+
test('single static JSXText gets the marker', () => {
|
|
44
|
+
expect(stamped(withIds, 'h1')).toBe(true);
|
|
45
|
+
expect(stamped(withIds, 'button')).toBe(true); // whitespace-framed text
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('multi-line static JSXText gets the marker', () => {
|
|
49
|
+
expect(stamped(withIds, 'p')).toBe(true); // first <p> — static prose
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('mixed static+expression content does NOT get the marker', () => {
|
|
53
|
+
// <p>Total: {1 + 1} items</p> — renders as leaf-looking text nodes, but
|
|
54
|
+
// the source is mixed → applyTextEdit refuses → must not be offered.
|
|
55
|
+
const second = withIds.match(/<p[^>]*>Total:/);
|
|
56
|
+
if (!second) throw new Error('mixed <p> not found');
|
|
57
|
+
expect(second[0].includes('data-cd-editable')).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('dynamic expression content does NOT get the marker', () => {
|
|
61
|
+
expect(stamped(withIds, 'span')).toBe(false); // {title}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("{'string literal'} single child DOES get the marker (DDR-150 P1)", () => {
|
|
65
|
+
expect(stamped(withIds, 'h2')).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('containers and empty elements do NOT get the marker', () => {
|
|
69
|
+
expect(stamped(withIds, 'section')).toBe(false); // element child
|
|
70
|
+
const selfClosed = withIds.match(/<div [^>]*\/>/);
|
|
71
|
+
if (!selfClosed) throw new Error('self-closed div not found');
|
|
72
|
+
expect(selfClosed[0].includes('data-cd-editable')).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('marker agrees with applyTextEdit acceptance for every stamped element', () => {
|
|
76
|
+
// Every data-cd-id that carries the marker must be accepted by the
|
|
77
|
+
// engine; every one without it (that has text-ish content) refused.
|
|
78
|
+
const opens = withIds.match(/<[a-z][^>]*data-cd-id="[^"]+"[^>]*>/g) ?? [];
|
|
79
|
+
for (const open of opens) {
|
|
80
|
+
const id = /data-cd-id="([^"]+)"/.exec(open)?.[1];
|
|
81
|
+
if (!id) continue;
|
|
82
|
+
const kind = /data-cd-editable="(text|var)"/.exec(open)?.[1];
|
|
83
|
+
let accepted = true;
|
|
84
|
+
try {
|
|
85
|
+
applyTextEdit('/virtual/Fixture.tsx', withIds, id, 'replacement');
|
|
86
|
+
} catch {
|
|
87
|
+
accepted = false;
|
|
88
|
+
}
|
|
89
|
+
// `text` (literal) MUST be accepted with no runtime context. `var`
|
|
90
|
+
// (a {variable}) is traced back at commit and needs occurrence/before —
|
|
91
|
+
// its acceptance is covered by dynamic-text-edit.test.ts, so only assert
|
|
92
|
+
// it isn't offered where the engine would hard-refuse a literal edit.
|
|
93
|
+
// Unmarked text-ish elements MUST be refused (no dead-end editor).
|
|
94
|
+
if (kind === 'text') {
|
|
95
|
+
expect(`${open} accepted=${accepted}`).toBe(`${open} accepted=true`);
|
|
96
|
+
} else if (!kind) {
|
|
97
|
+
expect(`${open} accepted=${accepted}`).toBe(`${open} accepted=false`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('applyTextEdit sibling integrity (Task 6.3)', () => {
|
|
104
|
+
test('editing the h1 leaves every sibling byte-identical', () => {
|
|
105
|
+
const { withIds } = transpileCanvasSource('/virtual/Fixture.tsx', FIXTURE);
|
|
106
|
+
const h1Id = /<h1[^>]*data-cd-id="([^"]+)"/.exec(withIds)?.[1];
|
|
107
|
+
if (!h1Id) throw new Error('no h1 id');
|
|
108
|
+
const { source: out } = applyTextEdit('/virtual/Fixture.tsx', withIds, h1Id, 'New title');
|
|
109
|
+
// The h1 text changed…
|
|
110
|
+
expect(out).toMatch(/<h1[^>]*data-cd-editable="text"/);
|
|
111
|
+
expect(out).toContain('New title');
|
|
112
|
+
expect(out).not.toContain('Maude desktop E2E');
|
|
113
|
+
// …and EVERYTHING ELSE is byte-identical: strip the h1 element line from
|
|
114
|
+
// both versions and compare the rest wholesale.
|
|
115
|
+
const strip = (s: string) => s.replace(/<h1[^>]*>[^<]*<\/h1>/, '<h1/>');
|
|
116
|
+
expect(strip(out)).toBe(strip(withIds));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('editing the multi-line p preserves its own whitespace framing', () => {
|
|
120
|
+
const { withIds } = transpileCanvasSource('/virtual/Fixture.tsx', FIXTURE);
|
|
121
|
+
const pId = /<p[^>]*data-cd-id="([^"]+)"[^>]*>\s*\n\s*Deterministic/.exec(withIds)?.[1];
|
|
122
|
+
if (!pId) throw new Error('no p id');
|
|
123
|
+
const { source: out } = applyTextEdit('/virtual/Fixture.tsx', withIds, pId, 'Short.');
|
|
124
|
+
// Leading/trailing whitespace of the JSXText survives (JSX indentation).
|
|
125
|
+
expect(out).toMatch(/<p[^>]*>\s*\n\s*Short\.\s*\n\s*<\/p>/);
|
|
126
|
+
// Siblings untouched.
|
|
127
|
+
expect(out).toMatch(/<h1[^>]*data-cd-editable="text"/);
|
|
128
|
+
expect(out).toContain('Maude desktop E2E');
|
|
129
|
+
expect(out).toContain('Total: {1 + 1} items');
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -124,4 +124,61 @@ export default function Canvas(){ return (<DesignCanvas>
|
|
|
124
124
|
expect(reel.total).toBe(120);
|
|
125
125
|
expect(reel.audio).toHaveLength(1);
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
test('a clips array built via .flatMap() collapses to one generic row (documents the parser limit — see video-comp SKILL.md "Never build ... with .map()/.flatMap()")', () => {
|
|
129
|
+
const src = `
|
|
130
|
+
const CLIP = 60; const XF = 15;
|
|
131
|
+
const clips = ['assets/a.mp4', 'assets/b.mp4', 'assets/c.mp4', 'assets/d.mp4'];
|
|
132
|
+
const TOTAL = clips.length * CLIP - (clips.length - 1) * XF;
|
|
133
|
+
const ReelClip = ({ src }) => <AbsoluteFill/>;
|
|
134
|
+
function Reel(){
|
|
135
|
+
return (<AbsoluteFill><TransitionSeries>
|
|
136
|
+
{clips.flatMap((src, i) => {
|
|
137
|
+
const seq = <TransitionSeries.Sequence key={\`s\${i}\`} durationInFrames={CLIP}><ReelClip src={src}/></TransitionSeries.Sequence>;
|
|
138
|
+
if (i === clips.length - 1) return [seq];
|
|
139
|
+
return [seq, <TransitionSeries.Transition key={\`t\${i}\`} presentation={fade()} timing={linearTiming({ durationInFrames: XF })}/>];
|
|
140
|
+
})}
|
|
141
|
+
</TransitionSeries></AbsoluteFill>);
|
|
142
|
+
}
|
|
143
|
+
`;
|
|
144
|
+
// Only ONE literal <TransitionSeries.Sequence> occurrence exists in the
|
|
145
|
+
// source text (written once inside the callback) — not the 4 clips that
|
|
146
|
+
// exist at runtime — and its durationInFrames={CLIP} is fine but the
|
|
147
|
+
// array-driven TOTAL isn't resolvable arithmetic, so scopedTotal falls
|
|
148
|
+
// back to the derived span.
|
|
149
|
+
const r = parseCompTimeline(src, 0);
|
|
150
|
+
expect(r.sequences).toHaveLength(1);
|
|
151
|
+
expect(r.sequences[0]).toMatchObject({ label: 'ReelClip', duration: 60 });
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('the corrected 4-literal-block worked example (video-comp SKILL.md) parses all 4 named clips + the literal-sum total', () => {
|
|
155
|
+
const src = `
|
|
156
|
+
const XF = 15;
|
|
157
|
+
const TOTAL = 60 + 60 + 60 + 60 - XF * 3;
|
|
158
|
+
const Clip = ({ src, label }) => <AbsoluteFill/>;
|
|
159
|
+
const Reel = () => (
|
|
160
|
+
<AbsoluteFill>
|
|
161
|
+
<TransitionSeries>
|
|
162
|
+
<TransitionSeries.Sequence name="clip-1" durationInFrames={60}><Clip src="assets/a.mp4" label="01" /></TransitionSeries.Sequence>
|
|
163
|
+
<TransitionSeries.Transition presentation={fade()} timing={linearTiming({ durationInFrames: XF })} />
|
|
164
|
+
<TransitionSeries.Sequence name="clip-2" durationInFrames={60}><Clip src="assets/b.mp4" label="02" /></TransitionSeries.Sequence>
|
|
165
|
+
<TransitionSeries.Transition presentation={fade()} timing={linearTiming({ durationInFrames: XF })} />
|
|
166
|
+
<TransitionSeries.Sequence name="clip-3" durationInFrames={60}><Clip src="assets/c.mp4" label="03" /></TransitionSeries.Sequence>
|
|
167
|
+
<TransitionSeries.Transition presentation={fade()} timing={linearTiming({ durationInFrames: XF })} />
|
|
168
|
+
<TransitionSeries.Sequence name="clip-4" durationInFrames={60}><Clip src="assets/d.mp4" label="04" /></TransitionSeries.Sequence>
|
|
169
|
+
</TransitionSeries>
|
|
170
|
+
</AbsoluteFill>
|
|
171
|
+
);
|
|
172
|
+
`;
|
|
173
|
+
const r = parseCompTimeline(src, 0);
|
|
174
|
+
expect(r.sequences).toHaveLength(4);
|
|
175
|
+
expect(r.sequences.map((s) => s.label)).toEqual(['clip-1', 'clip-2', 'clip-3', 'clip-4']);
|
|
176
|
+
expect(r.sequences.map((s) => s.duration)).toEqual([60, 60, 60, 60]);
|
|
177
|
+
// Sequence 1 at 0; each next clip starts durationInFrames - XF after the
|
|
178
|
+
// previous (the transition overlaps back), same crossfade math as the
|
|
179
|
+
// existing 2-clip test above.
|
|
180
|
+
expect(r.sequences.map((s) => s.from)).toEqual([0, 45, 90, 135]);
|
|
181
|
+
// 60*4 - 15*3 = 195, and it's a literal-arithmetic sum the parser resolves.
|
|
182
|
+
expect(r.total).toBe(195);
|
|
183
|
+
});
|
|
127
184
|
});
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// "+ Element" affordance's anchor resolver: appends relative to the ACTIVE
|
|
3
3
|
// artboard's last existing child (mirrors the context-menu's element-relative
|
|
4
4
|
// insert, since an artboard's own `<article data-dc-screen>` carries no
|
|
5
|
-
// `data-cd-id` of its own — DCArtboard doesn't forward it to the DOM)
|
|
5
|
+
// `data-cd-id` of its own — DCArtboard doesn't forward it to the DOM), falling
|
|
6
|
+
// back to the artboard's own `data-dc-screen` id (inside-end) when it has no
|
|
7
|
+
// such child yet — the empty-artboard fix for the previously-silent no-op.
|
|
6
8
|
//
|
|
7
9
|
// Needs a live DOM (querySelector/:scope) — register happy-dom for this file
|
|
8
10
|
// only, like canvas-hmr-runtime.test.tsx.
|
|
@@ -62,13 +64,13 @@ describe('tool-palette / resolveInsertAnchor', () => {
|
|
|
62
64
|
expect(resolveInsertAnchor(document)).toEqual({ refId: '11111111', position: 'after' });
|
|
63
65
|
});
|
|
64
66
|
|
|
65
|
-
test('
|
|
67
|
+
test('falls back to the artboard id (inside-end) for an EMPTY artboard — no longer a silent no-op', () => {
|
|
66
68
|
document.body.innerHTML = `
|
|
67
69
|
<article data-dc-screen="a1" aria-current="true">
|
|
68
70
|
<div class="dc-artboard-body"></div>
|
|
69
71
|
</article>
|
|
70
72
|
`;
|
|
71
|
-
expect(resolveInsertAnchor(document)).
|
|
73
|
+
expect(resolveInsertAnchor(document)).toEqual({ artboardId: 'a1', position: 'inside-end' });
|
|
72
74
|
});
|
|
73
75
|
|
|
74
76
|
test('returns null when there is no artboard at all', () => {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { describe, expect, test } from 'bun:test';
|
|
9
9
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
10
10
|
import { TourOverlay } from '../client/tour/overlay.jsx';
|
|
11
|
+
import { QUICK_SETUP_TOUR } from '../client/tour/quick-setup-tour.js';
|
|
11
12
|
|
|
12
13
|
const STEP = { target: '.x', title: 'Welcome', body: 'Body copy here' };
|
|
13
14
|
|
|
@@ -61,6 +62,21 @@ describe('TourOverlay', () => {
|
|
|
61
62
|
expect(html).toContain('Working together');
|
|
62
63
|
});
|
|
63
64
|
|
|
65
|
+
test('QUICK_SETUP_TOUR (DDR-166 plan, Phase 2 / T7) is a well-formed, renderable deck', () => {
|
|
66
|
+
expect(QUICK_SETUP_TOUR.length).toBeGreaterThan(1);
|
|
67
|
+
for (const step of QUICK_SETUP_TOUR) {
|
|
68
|
+
expect(typeof step.title).toBe('string');
|
|
69
|
+
expect(step.title.length).toBeGreaterThan(0);
|
|
70
|
+
expect(typeof step.body).toBe('string');
|
|
71
|
+
expect(step.body.length).toBeGreaterThan(0);
|
|
72
|
+
}
|
|
73
|
+
const html = renderToStaticMarkup(
|
|
74
|
+
<TourOverlay open steps={QUICK_SETUP_TOUR} onClose={() => {}} onComplete={() => {}} />
|
|
75
|
+
);
|
|
76
|
+
expect(html).toContain('mdcc-tour__title');
|
|
77
|
+
expect(html).toContain(`1 / ${QUICK_SETUP_TOUR.length}`);
|
|
78
|
+
});
|
|
79
|
+
|
|
64
80
|
test('closed or empty renders nothing', () => {
|
|
65
81
|
expect(
|
|
66
82
|
renderToStaticMarkup(
|
|
@@ -208,4 +208,37 @@ describe('cross-iframe persistence (loadStackState / saveStackState)', () => {
|
|
|
208
208
|
expect(reloaded.past.length).toBe(2);
|
|
209
209
|
expect(reloaded.future.length).toBe(1);
|
|
210
210
|
});
|
|
211
|
+
|
|
212
|
+
test('survives a WINDOW-STORE WIPE via sessionStorage (split-origin iframe reload)', () => {
|
|
213
|
+
// Split-origin canvas: the store lives on the iframe window, which a full
|
|
214
|
+
// reload (the HMR reload a `.tsx` text edit triggers) destroys. The durable
|
|
215
|
+
// sessionStorage layer must restore it. Install a fake window + sessionStorage.
|
|
216
|
+
const backing = new Map<string, string>();
|
|
217
|
+
const fakeSS = {
|
|
218
|
+
getItem: (k: string) => backing.get(k) ?? null,
|
|
219
|
+
setItem: (k: string, v: string) => backing.set(k, v),
|
|
220
|
+
removeItem: (k: string) => backing.delete(k),
|
|
221
|
+
key: (i: number) => [...backing.keys()][i] ?? null,
|
|
222
|
+
clear: () => backing.clear(),
|
|
223
|
+
get length() {
|
|
224
|
+
return backing.size;
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
const g = globalThis as unknown as { window?: unknown };
|
|
228
|
+
const prev = g.window;
|
|
229
|
+
g.window = { sessionStorage: fakeSS }; // no `top` → store host = this window
|
|
230
|
+
try {
|
|
231
|
+
_clearStackStore();
|
|
232
|
+
saveStackState('ui/Split.tsx', { past: [rec('kept')], future: [] });
|
|
233
|
+
expect(backing.size).toBe(1); // mirrored to sessionStorage
|
|
234
|
+
// Simulate the iframe reload: the window's in-memory Map is gone.
|
|
235
|
+
(g.window as { __maude_undo_stacks?: unknown }).__maude_undo_stacks = new Map();
|
|
236
|
+
const restored = loadStackState('ui/Split.tsx');
|
|
237
|
+
expect(restored.past.length).toBe(1);
|
|
238
|
+
expect(restored.past[0]?.label).toBe('kept');
|
|
239
|
+
} finally {
|
|
240
|
+
_clearStackStore();
|
|
241
|
+
g.window = prev;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
211
244
|
});
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
// the stored type AND category (→ which cap applies); a header lie / SVG /
|
|
6
6
|
// script is caught here;
|
|
7
7
|
// (2) the streaming POST endpoint (boots a real server) — video/audio happy
|
|
8
|
-
// path, per-category cap (image
|
|
9
|
-
// MAUDE_ASSET_MAX_VIDEO_BYTES override), content-addressed dedupe.
|
|
8
|
+
// path, per-category cap (image stays tighter than video; video honors
|
|
9
|
+
// the MAUDE_ASSET_MAX_VIDEO_BYTES override), content-addressed dedupe.
|
|
10
10
|
|
|
11
11
|
import { describe, expect, test } from 'bun:test';
|
|
12
12
|
import { existsSync, readdirSync } from 'node:fs';
|
|
13
13
|
import { join } from 'node:path';
|
|
14
14
|
|
|
15
|
-
import { assetCapForCategory, sniffAssetType } from '../api.ts';
|
|
15
|
+
import { ASSET_MAX_BYTES, assetCapForCategory, sniffAssetType } from '../api.ts';
|
|
16
16
|
import { bootServer, killProc, makeSandbox, nextPort } from './_helpers.ts';
|
|
17
17
|
|
|
18
18
|
// ── Minimal valid magic-byte headers (padded so the ≥12-byte sniff has data). ──
|
|
@@ -59,8 +59,8 @@ describe('video-asset / sniffAssetType (the widened type gate)', () => {
|
|
|
59
59
|
expect(sniffAssetType(pad([...ascii('RIFF'), 0, 0, 0, 0, ...ascii('AVI ')]))).toBeNull();
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
-
test('caps: image
|
|
63
|
-
expect(assetCapForCategory('image')).toBe(
|
|
62
|
+
test('caps: image stays tight, video/audio share the larger cap', () => {
|
|
63
|
+
expect(assetCapForCategory('image')).toBe(ASSET_MAX_BYTES);
|
|
64
64
|
expect(assetCapForCategory('video')).toBeGreaterThan(assetCapForCategory('image'));
|
|
65
65
|
expect(assetCapForCategory('audio')).toBe(assetCapForCategory('video'));
|
|
66
66
|
});
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
// (DOM containment resolution) and the `__maude_render_video__` bridge's
|
|
9
9
|
// routing contract — it must merge the registered comp's component/meta with
|
|
10
10
|
// the caller's opts and delegate to the injected `__maudeRenderVideo__`, and
|
|
11
|
-
// fail clearly when either the comp or the render-lib injection is missing
|
|
11
|
+
// fail clearly when either the comp or the render-lib injection is missing, and
|
|
12
|
+
// PROPAGATE a renderer failure as a rejection (the contract the capture shim's
|
|
13
|
+
// try/catch relies on to fall back to frame-step screenshots — RCA
|
|
14
|
+
// issue-video-mp4-rendermediaonweb-stack-overflow). The shim-level fallback
|
|
15
|
+
// itself (renderMediaOnWeb throws → frame-step, video-only, degraded=true) is
|
|
16
|
+
// verified live against a deep-precompositing comp — see that RCA's repro.
|
|
12
17
|
|
|
13
18
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'bun:test';
|
|
14
19
|
import { GlobalRegistrator } from '@happy-dom/global-registrator';
|
|
@@ -146,4 +151,21 @@ describe('__maude_render_video__ bridge', () => {
|
|
|
146
151
|
/render-lib bundle not injected/i
|
|
147
152
|
);
|
|
148
153
|
});
|
|
154
|
+
|
|
155
|
+
test('propagates a renderer failure (e.g. stack overflow) as a rejection the shim can catch', async () => {
|
|
156
|
+
// The capture shim (_video-playwright.mjs) wraps the __maude_render_video__
|
|
157
|
+
// page.evaluate in try/catch and degrades to frame-step screenshots when it
|
|
158
|
+
// rejects. That only works if the bridge surfaces __maudeRenderVideo__'s
|
|
159
|
+
// throw as a rejection rather than swallowing it — the exact failure mode
|
|
160
|
+
// of the RCA (@remotion/web-renderer overflowing on deep precompositing).
|
|
161
|
+
const w = window as unknown as RenderWindow;
|
|
162
|
+
installMaudeSeekBridge();
|
|
163
|
+
registerComp('reel', { fps: 30, durationInFrames: 228, width: 960, height: 540 }, () => null);
|
|
164
|
+
w.__maudeRenderVideo__ = async () => {
|
|
165
|
+
throw new RangeError('Maximum call stack size exceeded');
|
|
166
|
+
};
|
|
167
|
+
await expect(w.__maude_render_video__?.('reel', { container: 'mp4' })).rejects.toThrow(
|
|
168
|
+
/Maximum call stack size exceeded/
|
|
169
|
+
);
|
|
170
|
+
});
|
|
149
171
|
});
|