@1agh/maude 0.58.1 → 0.58.3

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.
Files changed (119) hide show
  1. package/apps/studio/.ai/cache/_stats.json +1 -1
  2. package/apps/studio/acp/bridge.ts +165 -15
  3. package/apps/studio/annotations-bindings.ts +83 -4
  4. package/apps/studio/api.ts +6 -1
  5. package/apps/studio/bin/_fetch-asset.mjs +169 -5
  6. package/apps/studio/bin/_import-asset.mjs +72 -0
  7. package/apps/studio/bin/_import-figma.mjs +1121 -0
  8. package/apps/studio/bin/_video-playwright.mjs +86 -3
  9. package/apps/studio/bin/import-figma.sh +38 -0
  10. package/apps/studio/bin/read-annotations.mjs +11 -1
  11. package/apps/studio/bun.lock +16 -22
  12. package/apps/studio/canvas-edit.ts +29 -5
  13. package/apps/studio/client/app.jsx +129 -23
  14. package/apps/studio/client/export-center.jsx +42 -4
  15. package/apps/studio/client/panels/ChatPanel.jsx +25 -2
  16. package/apps/studio/client/panels/CloudBar.jsx +92 -1
  17. package/apps/studio/client/panels/FigmaImportPanel.jsx +264 -0
  18. package/apps/studio/client/panels/GitPanel.jsx +26 -6
  19. package/apps/studio/client/panels/SettingsPanel.jsx +181 -0
  20. package/apps/studio/client/panels/SetupChecklist.jsx +26 -2
  21. package/apps/studio/client/panels/TimelinePanel.jsx +2 -2
  22. package/apps/studio/client/panels/timeline-parse.js +3 -3
  23. package/apps/studio/client/styles/3-shell-maude.css +7 -0
  24. package/apps/studio/client/styles/4-components.css +134 -0
  25. package/apps/studio/client/styles/6-acp-chat.css +12 -0
  26. package/apps/studio/clip-ops.ts +93 -17
  27. package/apps/studio/cloud/endpoints.ts +78 -10
  28. package/apps/studio/cloud/renew.ts +183 -0
  29. package/apps/studio/context.ts +2 -1
  30. package/apps/studio/dist/client.bundle.js +1491 -1491
  31. package/apps/studio/dist/runtime/@remotion_media.js +56 -136
  32. package/apps/studio/dist/runtime/@remotion_player.js +18 -18
  33. package/apps/studio/dist/runtime/@remotion_transitions.js +9 -9
  34. package/apps/studio/dist/runtime/@remotion_transitions_clock-wipe.js +1 -1
  35. package/apps/studio/dist/runtime/remotion.js +12 -12
  36. package/apps/studio/dist/styles.css +1 -1
  37. package/apps/studio/exporters/_browser-bundles.ts +20 -6
  38. package/apps/studio/exporters/_runtime.ts +19 -0
  39. package/apps/studio/exporters/degraded.ts +92 -0
  40. package/apps/studio/exporters/index.ts +5 -0
  41. package/apps/studio/exporters/jobs.ts +19 -0
  42. package/apps/studio/exporters/unsupported-media.ts +170 -0
  43. package/apps/studio/exporters/video-encode-lib.ts +27 -1
  44. package/apps/studio/exporters/video-render-lib.ts +6 -0
  45. package/apps/studio/exporters/video.ts +62 -1
  46. package/apps/studio/figma/assets.test.ts +372 -0
  47. package/apps/studio/figma/assets.ts +398 -0
  48. package/apps/studio/figma/client.test.ts +395 -0
  49. package/apps/studio/figma/client.ts +513 -0
  50. package/apps/studio/figma/comments-to-strokes.test.ts +194 -0
  51. package/apps/studio/figma/comments-to-strokes.ts +173 -0
  52. package/apps/studio/figma/endpoints.ts +200 -0
  53. package/apps/studio/figma/sanitize.test.ts +256 -0
  54. package/apps/studio/figma/sanitize.ts +315 -0
  55. package/apps/studio/figma/style-map.ts +352 -0
  56. package/apps/studio/figma/to-artboard.test.ts +808 -0
  57. package/apps/studio/figma/to-artboard.ts +701 -0
  58. package/apps/studio/figma/to-render.test.ts +180 -0
  59. package/apps/studio/figma/to-render.ts +306 -0
  60. package/apps/studio/figma/to-strokes-roundtrip.test.ts +152 -0
  61. package/apps/studio/figma/to-strokes.test.ts +705 -0
  62. package/apps/studio/figma/to-strokes.ts +749 -0
  63. package/apps/studio/figma/to-tokens.test.ts +321 -0
  64. package/apps/studio/figma/to-tokens.ts +305 -0
  65. package/apps/studio/figma/types.ts +539 -0
  66. package/apps/studio/figma/url.test.ts +167 -0
  67. package/apps/studio/figma/url.ts +160 -0
  68. package/apps/studio/http.ts +129 -0
  69. package/apps/studio/sync/asset-push.ts +124 -0
  70. package/apps/studio/sync/canvas-path.ts +329 -0
  71. package/apps/studio/sync/codec.ts +42 -0
  72. package/apps/studio/sync/connection-state.ts +11 -0
  73. package/apps/studio/sync/hub-link.ts +63 -7
  74. package/apps/studio/sync/hubs-config.ts +31 -3
  75. package/apps/studio/sync/index.ts +755 -32
  76. package/apps/studio/sync/migrate-flat-fallback.ts +121 -0
  77. package/apps/studio/sync/presentation.ts +45 -1
  78. package/apps/studio/sync/projection.ts +11 -1
  79. package/apps/studio/sync/remote-docs.ts +122 -15
  80. package/apps/studio/sync/supervisor.ts +5 -1
  81. package/apps/studio/sync/workspace-signin.ts +7 -3
  82. package/apps/studio/test/acp-bridge-lifetime.test.ts +106 -0
  83. package/apps/studio/test/annotations-bindings.test.ts +150 -12
  84. package/apps/studio/test/canvas-create-api.test.ts +4 -1
  85. package/apps/studio/test/canvas-origin-gate.test.ts +13 -0
  86. package/apps/studio/test/capture-determinism-shape.test.ts +135 -0
  87. package/apps/studio/test/clip-addressing.test.ts +6 -1
  88. package/apps/studio/test/clip-ops.test.ts +5 -1
  89. package/apps/studio/test/cloud-endpoints.test.ts +96 -0
  90. package/apps/studio/test/cloud-renew.test.ts +205 -0
  91. package/apps/studio/test/cloud-shell-surfaces.test.ts +11 -2
  92. package/apps/studio/test/comment-relay-origin-gate.test.ts +117 -0
  93. package/apps/studio/test/comments-fs-rebroadcast.test.ts +155 -0
  94. package/apps/studio/test/exporters/degraded-propagation.test.ts +123 -0
  95. package/apps/studio/test/exporters/unsupported-media.test.ts +123 -0
  96. package/apps/studio/test/fetch-asset-gate.test.ts +189 -0
  97. package/apps/studio/test/figma-provenance.test.ts +108 -0
  98. package/apps/studio/test/figma-routes.test.ts +294 -0
  99. package/apps/studio/test/fixtures/mock-acp-agent-wedged.mjs +37 -0
  100. package/apps/studio/test/git-cloud-posture.test.ts +50 -0
  101. package/apps/studio/test/hub-link.test.ts +11 -0
  102. package/apps/studio/test/import-figma.test.ts +479 -0
  103. package/apps/studio/test/sync-asset-push.test.ts +124 -0
  104. package/apps/studio/test/sync-canvas-path.test.ts +200 -0
  105. package/apps/studio/test/sync-connection-state.test.ts +13 -0
  106. package/apps/studio/test/sync-hubs-config.test.ts +5 -0
  107. package/apps/studio/test/sync-migrate-flat-fallback.test.ts +98 -0
  108. package/apps/studio/test/sync-path-pull.test.ts +465 -0
  109. package/apps/studio/test/sync-presentation.test.ts +77 -0
  110. package/apps/studio/test/sync-remote-docs.test.ts +55 -3
  111. package/apps/studio/test/sync-runtime.test.ts +434 -1
  112. package/apps/studio/test/video-comp.test.ts +23 -1
  113. package/apps/studio/test/workspace-containment.test.ts +1 -0
  114. package/apps/studio/video-comp.tsx +70 -6
  115. package/apps/studio/whats-new.json +36 -0
  116. package/apps/studio/workspace-mode.ts +4 -0
  117. package/cli/commands/design.mjs +8 -0
  118. package/package.json +8 -8
  119. package/plugins/flow/.claude-plugin/config.schema.json +3 -3
@@ -25,7 +25,7 @@
25
25
  import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
26
26
  import { join } from 'node:path';
27
27
 
28
- import { launchChromium } from './_pw-launch.mjs';
28
+ import { assertRenderOutputSizeOk, launchChromium } from './_pw-launch.mjs';
29
29
 
30
30
  const args = Object.fromEntries(
31
31
  process.argv.slice(2).reduce((acc, cur, i, all) => {
@@ -71,7 +71,26 @@ const licenseKey = licenseKeyArg || 'free-license';
71
71
  /** Wait a real turn of the event loop + a frame so a seek settles before shot. */
72
72
  const SETTLE_MS = 16;
73
73
 
74
- const browser = await launchChromium();
74
+ // GPU opt-in, measured not assumed (2026-08-09, M-series mac, 1920x1080):
75
+ //
76
+ // config h264 prefer-hardware png jpeg q90
77
+ // headless-shell (default) FALSE 51ms/187KB 33ms/43KB
78
+ // headless-shell --enable-gpu true 34ms/84KB 33ms/43KB
79
+ // full chromium / real Chrome true 34-49ms 17-33ms
80
+ //
81
+ // So today's engine is the ONE configuration that cannot reach a hardware
82
+ // encoder at all — `chrome-headless-shell` forces ANGLE/SwiftShader, and
83
+ // Chromium then reports ACCELERATED_VIDEO_ENCODE as disabled. `--enable-gpu`
84
+ // flips that.
85
+ //
86
+ // It is NOT on by default, deliberately: with the GPU the same page rasterizes
87
+ // to a materially different PNG (84KB vs 187KB), i.e. it is a VISUAL change to
88
+ // every export, and encode is not the bottleneck anyway (~50ms of a ~1050ms
89
+ // frame — see the MAUDE_TIMING line). Changing how a design tool renders is not
90
+ // something to smuggle in behind a perf flag. Opt in with MAUDE_CAPTURE_GPU=1.
91
+ const gpuArgs =
92
+ process.env.MAUDE_CAPTURE_GPU === '1' ? ['--enable-gpu', '--ignore-gpu-blocklist'] : [];
93
+ const browser = await launchChromium(gpuArgs.length ? { args: gpuArgs } : undefined);
75
94
  try {
76
95
  const ctx = await browser.newContext({
77
96
  viewport: { width: 1440, height: 900 },
@@ -199,6 +218,19 @@ try {
199
218
  audioCodec: rendered.audioCodec,
200
219
  };
201
220
  const ms = Date.now() - t0;
221
+ // Same shape as the frame-step line, so the two paths are directly
222
+ // comparable — that comparison is the whole speed story (~45 s vs ~37 min
223
+ // on the same canvas, per RCA issue-mp4-audio-export-html5audio-silent-degrade).
224
+ console.log(
225
+ `MAUDE_TIMING ${JSON.stringify({
226
+ path: 'renderer',
227
+ frames: frameCount,
228
+ totalMs: ms,
229
+ msPerFrame: Math.round((ms / Math.max(1, frameCount)) * 10) / 10,
230
+ scale: deviceScaleFactor,
231
+ audioCodec: rendered.audioCodec,
232
+ })}`
233
+ );
202
234
  console.error(
203
235
  `✓ rendered ${rendered.container}/${rendered.videoCodec}` +
204
236
  `${rendered.audioCodec ? `+${rendered.audioCodec}` : ' (muted)'} → ${out} (${rendered.bytes} B) in ${ms}ms`
@@ -368,6 +400,11 @@ async function frameStepCapture({
368
400
  // frame is drawn down to the native artboard size (the "tiny resolution" bug:
369
401
  // scale was accepted but never applied to the encode). scale=2 → 960×540 comp
370
402
  // exports at 1920×1080. Even dims (H.264 requires width/height divisible by 2).
403
+ // The video path is the ONE capture path that holds a full-resolution surface
404
+ // across thousands of frames, and it was the only one with no output-size
405
+ // guard — _pdf-playwright.mjs has enforced this since it existed. A scale-3
406
+ // 1080p comp allocates ~74MB per frame here.
407
+ assertRenderOutputSizeOk(clip.width, clip.height, deviceScaleFactor, '_video-playwright');
371
408
  const outW = Math.max(2, Math.round((clip.width * deviceScaleFactor) / 2) * 2);
372
409
  const outH = Math.max(2, Math.round((clip.height * deviceScaleFactor) / 2) * 2);
373
410
 
@@ -398,16 +435,27 @@ async function frameStepCapture({
398
435
  console.error(`encoder: ${started.container} / ${started.codec} @ ${outW}×${outH}`);
399
436
  }
400
437
 
438
+ // Per-stage accumulators. "The export takes 15 minutes" was, for a long time,
439
+ // answerable only by guessing which stage owned the second — so every
440
+ // optimization was aimed at a suspicion. One line at the end fixes that.
441
+ const stageMs = { seek: 0, settle: 0, screenshot: 0, encode: 0 };
401
442
  for (let f = 0; f < frameCount; f += 1) {
443
+ const tSeek = Date.now();
402
444
  await seekFrame(page, f, fps, mode);
445
+ stageMs.seek += Date.now() - tSeek;
446
+ const tSettle = Date.now();
403
447
  await page.waitForTimeout(SETTLE_MS);
448
+ stageMs.settle += Date.now() - tSettle;
449
+ const tShot = Date.now();
404
450
  const shot = await page.screenshot({ clip });
451
+ stageMs.screenshot += Date.now() - tShot;
405
452
  if (dump) {
406
453
  const p = join(dump, `frame-${String(f).padStart(5, '0')}.png`);
407
454
  writeFileSync(p, shot);
408
455
  framePaths.push(p);
409
456
  }
410
457
  if (encoding) {
458
+ const tEnc = Date.now();
411
459
  const b64 = shot.toString('base64');
412
460
  await page.evaluate(
413
461
  async ({ b64, isGif }) => {
@@ -416,6 +464,11 @@ async function frameStepCapture({
416
464
  },
417
465
  { b64, isGif }
418
466
  );
467
+ // NB: this stage is the double CDP crossing — the screenshot bytes go out
468
+ // to node as base64 and straight back into the same page, to an encoder
469
+ // that lives in that page. Its share of the total is what decides whether
470
+ // transport work is worth doing at all.
471
+ stageMs.encode += Date.now() - tEnc;
419
472
  }
420
473
  // Machine-readable progress (stdout) — spawnShim parses `MAUDE_PROGRESS`
421
474
  // lines → onProgress → job.progress → the live bar in the Exports panel, so
@@ -426,6 +479,18 @@ async function frameStepCapture({
426
479
  if (f % 30 === 0) console.error(`frame ${f + 1}/${frameCount}`);
427
480
  }
428
481
 
482
+ // A seek that never landed means at least one encoded frame shows the wrong
483
+ // content. There is no way to tell WHICH from the outside and no way for the
484
+ // user to notice, so the export dies here rather than shipping a file that
485
+ // looks fine. Counted in-page by the seek bridge (video-comp.tsx).
486
+ const seekFailures = await page.evaluate(() => window.__maude_seek_failures__ ?? 0);
487
+ if (seekFailures > 0) {
488
+ throw new Error(
489
+ `${seekFailures} frame seek(s) never landed — the capture would contain stale ` +
490
+ 'frames. Refusing to encode. This usually means the comp failed to mount.'
491
+ );
492
+ }
493
+
429
494
  let result = { fps, frameCount, width: outW, height: outH, framePaths };
430
495
  // Degradation marker — this run only encodes video (no audio), so when we're
431
496
  // standing in for a failed audio renderer, tell the exporter the export is
@@ -448,6 +513,19 @@ async function frameStepCapture({
448
513
  console.error(
449
514
  `✓ captured ${frameCount} frames @ ${fps}fps (${clip.width}×${clip.height}) in ${ms}ms`
450
515
  );
516
+ // Machine-readable, filtered out of stdoutLines by _runtime.ts (the adapters
517
+ // parse the LAST stdout line as their summary, so this must never be it).
518
+ console.log(
519
+ `MAUDE_TIMING ${JSON.stringify({
520
+ path: 'frame-step',
521
+ frames: frameCount,
522
+ totalMs: ms,
523
+ msPerFrame: Math.round((ms / Math.max(1, frameCount)) * 10) / 10,
524
+ stageMs,
525
+ scale: deviceScaleFactor,
526
+ out: { width: outW, height: outH },
527
+ })}`
528
+ );
451
529
  // stdout = machine-readable summary for the exporter.
452
530
  console.log(JSON.stringify(result));
453
531
  }
@@ -486,6 +564,10 @@ async function seekFrame(page, frame, fps, mode) {
486
564
  return;
487
565
  }
488
566
  // comp mode — the seek bridge pauses + seeks the Player and resolves post-paint.
567
+ // It now REJECTS when a seek never lands (video-comp.tsx), instead of resolving
568
+ // as if it had; that rejection propagates out of this evaluate and fails the
569
+ // export, which is the point — a stale frame is a valid-looking file with the
570
+ // wrong pixels, and no downstream check would ever catch it.
489
571
  // A comp with classic remotion <Video>/<OffthreadVideo> renders a real <video>
490
572
  // whose seek is ASYNC — 2 rAF isn't enough, so wait for every video to land on
491
573
  // its frame (`seeked` / readyState) before the screenshot, else a stale frame
@@ -494,7 +576,8 @@ async function seekFrame(page, frame, fps, mode) {
494
576
  // on (verified across mid-clip + mid-transition frames — DDR-148 addendum).
495
577
  await page.evaluate(async (frame) => {
496
578
  if (typeof window.__maude_seek__ === 'function') {
497
- await window.__maude_seek__(frame);
579
+ // strict: a capture must never proceed on a seek that did not land.
580
+ await window.__maude_seek__(frame, { strict: true });
498
581
  }
499
582
  const vids = Array.from(document.querySelectorAll('video'));
500
583
  if (vids.length) {
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ # import-figma.sh — Figma / FigJam import (REST door). Thin shim over
3
+ # _import-figma.mjs; reached via `maude design import-figma` (DDR-062), never a
4
+ # raw bin path. See _import-figma.mjs + DDR-216 for the full architecture +
5
+ # security rationale. Requires bun — _import-figma.mjs imports the `.ts`
6
+ # annotation model and figma modules directly, same constraint as
7
+ # import-tokens.sh.
8
+ #
9
+ # All args are forwarded quoted ("$@") — never unquoted or eval-expanded. This
10
+ # matters more than usual here: a Figma URL is caller-supplied and a node id
11
+ # rides in its query string. The charset/shape validation itself happens in
12
+ # figma/url.ts, not in this shell (DDR-216 D4); the wrapper's ONLY job is safe
13
+ # argv passthrough.
14
+ #
15
+ # Usage:
16
+ # import-figma.sh --board <figjam-url> --root <repo> [--design-root .design]
17
+ # [--slug <name>] [--dry-run] [--json]
18
+ # import-figma.sh --frames <figma-url> --root <repo> (Phase 3)
19
+ # import-figma.sh --tokens <figma-url> --root <repo> (Phase 4)
20
+ #
21
+ # Needs a Figma personal access token with the `file_content:read` scope, added
22
+ # once in Settings.
23
+ #
24
+ # Exit: 0 ok · 1 other · 2 usage · 3 validation reject · 4 fetch/parse error ·
25
+ # 5 no token configured · 6 write/containment error.
26
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
27
+
28
+ case "$1" in
29
+ --help|-h) sed -n '2,25p' "$0" | sed 's/^# \?//'; exit 0 ;;
30
+ esac
31
+
32
+ if ! command -v bun >/dev/null 2>&1; then
33
+ echo "import-figma.sh: bun is required (hard dependency — see plugins/design/dependencies.json)." >&2
34
+ echo " Install: curl -fsSL https://bun.sh/install | bash" >&2
35
+ exit 1
36
+ fi
37
+
38
+ exec bun run "$SCRIPT_DIR/_import-figma.mjs" "$@"
@@ -106,6 +106,9 @@ Args:
106
106
 
107
107
  FigJam v3 additive fields per annotation: z (render order), groupIds (deepest →
108
108
  shallowest), author ("ai" for annotate-verb strokes), authorName/authorId
109
+ // (an IMPORTED stroke carries authorName "imported-figma" — DDR-216 D7, so a
110
+ // consuming skill can tell third-party content from what the user drew), the
111
+ // rendered fontSize for text/sticky (so 8 px is distinguishable from a heading)
109
112
  (human-drawn stickies), and from/to host ids on bound arrows.
110
113
 
111
114
  Section annotations additionally carry members: an array of every OTHER
@@ -210,6 +213,10 @@ function readText(attrs, inner) {
210
213
  h: null,
211
214
  text: decodeEntities(inner).trim(),
212
215
  color: attr(attrs, 'fill') || attr(attrs, 'stroke') || null,
216
+ // DDR-216 D6b / review F5 — a consuming skill must be able to see HOW BIG
217
+ // this renders. Without it, text at the 8 px floor is indistinguishable
218
+ // from a heading in the JSON an agent is handed.
219
+ fontSize: num(attr(attrs, 'font-size')),
213
220
  };
214
221
  if (anchorId) o.anchorId = anchorId;
215
222
  return o;
@@ -235,7 +242,8 @@ function readSticky(attrs, inner) {
235
242
  // Geometry off the inner <rect>; paper tint off the group fill; body off the
236
243
  // inner <text>. (annotations-layer.tsx strokeToSvgEl 'sticky' branch.)
237
244
  const rectAttrs = inner.match(/<rect\b([^>]*?)\/?>/)?.[1] ?? '';
238
- const body = inner.match(/<text\b[^>]*>([\s\S]*?)<\/text>/)?.[1] ?? '';
245
+ const textEl = inner.match(/<text\b([^>]*)>([\s\S]*?)<\/text>/);
246
+ const body = textEl?.[2] ?? '';
239
247
  return {
240
248
  tool: 'sticky',
241
249
  id: attr(attrs, 'data-id') || '',
@@ -245,6 +253,8 @@ function readSticky(attrs, inner) {
245
253
  h: num(attr(rectAttrs, 'height')),
246
254
  text: decodeEntities(body).trim(),
247
255
  color: attr(attrs, 'fill') || null,
256
+ // See readText — a consuming skill needs the rendered size.
257
+ fontSize: num(attr(textEl?.[1] ?? '', 'font-size')),
248
258
  };
249
259
  }
250
260
 
@@ -14,10 +14,10 @@
14
14
  "@mediabunny/aac-encoder": "1.50.7",
15
15
  "@mediabunny/flac-encoder": "1.50.7",
16
16
  "@mediabunny/mp3-encoder": "1.50.7",
17
- "@remotion/media": "4.0.486",
18
- "@remotion/player": "4.0.486",
19
- "@remotion/transitions": "4.0.486",
20
- "@remotion/web-renderer": "4.0.486",
17
+ "@remotion/media": "4.0.507",
18
+ "@remotion/player": "4.0.507",
19
+ "@remotion/transitions": "4.0.507",
20
+ "@remotion/web-renderer": "4.0.507",
21
21
  "dom-to-pptx": "^1.1.10",
22
22
  "dom-to-svg": "^0.12.2",
23
23
  "gifenc": "^1.0.3",
@@ -37,7 +37,7 @@
37
37
  "pptxgenjs": "^4.0.1",
38
38
  "react": "^19.2.7",
39
39
  "react-dom": "^19.2.7",
40
- "remotion": "4.0.486",
40
+ "remotion": "4.0.507",
41
41
  "svgo": "^4.0.1",
42
42
  "y-protocols": "^1.0.7",
43
43
  "yjs": "^13.6.31",
@@ -323,19 +323,19 @@
323
323
 
324
324
  "@radix-ui/rect": ["@radix-ui/rect@1.1.2", "", {}, "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA=="],
325
325
 
326
- "@remotion/licensing": ["@remotion/licensing@4.0.486", "", {}, "sha512-9viDTCmthh0LntxGQxKaArHPVW6wAc6FmoZfBNdIHfXoE9FCfhb+VXeJVhfx2Z6awPgu7vn93wstGBMussODOw=="],
326
+ "@remotion/licensing": ["@remotion/licensing@4.0.507", "", {}, "sha512-fdods4YD9c8Ww69wHdnmXpjx7W0rvct6B7gLb92cWsd+ITSUG3hKoh+NqSWXF0GK5bk8IFRmTtBAwJObxow9wA=="],
327
327
 
328
- "@remotion/media": ["@remotion/media@4.0.486", "", { "dependencies": { "mediabunny": "1.50.3", "remotion": "4.0.486", "zod": "4.3.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-vRglyMIvLDuVkj6r1miIcWDoVOoTHsWig4dMLM+F6Z5Z9BxVC95Vtlu8HjrtAvpV6K0AZ1uK3RhXemI+HybvCQ=="],
328
+ "@remotion/media": ["@remotion/media@4.0.507", "", { "dependencies": { "mediabunny": "1.50.8", "remotion": "4.0.507", "zod": "4.4.3" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-PpSbLq0x2E/yZGHyYcGsFpUWcocy+w2uV64qcTgASSN3Vd2ZSwNAaAGrNCUD9nN3EUaHjsoqheL/o8cRTZZ06g=="],
329
329
 
330
- "@remotion/paths": ["@remotion/paths@4.0.486", "", {}, "sha512-/qhmgyQ7/xUxbxnuqtZqfAjBzkV15Tvw73r0wKS82uVE+pBzlS14aucgoJcfksDv26VwCXn6BdHpUXUjXFCjhA=="],
330
+ "@remotion/paths": ["@remotion/paths@4.0.507", "", {}, "sha512-/2vXtdZDvpicamS30CmW+qYkA/yWhTUCk/5MysLExZYll8pqCR+BTlluRwpkfqhH0H4VJ4LwDoY1ZRd3MQ1HeQ=="],
331
331
 
332
- "@remotion/player": ["@remotion/player@4.0.486", "", { "dependencies": { "remotion": "4.0.486" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-S8wh+bS695gidwXYNnCjwZzJG9oPzyYBFMjpO0rlTbrNc3naIKT8UP443J0opitP4g8VuXnoIKObF7JzAfpe5A=="],
332
+ "@remotion/player": ["@remotion/player@4.0.507", "", { "dependencies": { "remotion": "4.0.507" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-ZAunyyCfOXSYh5sZrp+vFxZlSPatg5evmh7fHVCndAOWBUPIDVjdUWEM2D4i132MsN6GXasXDByi+6H0H+IkhQ=="],
333
333
 
334
- "@remotion/shapes": ["@remotion/shapes@4.0.486", "", { "dependencies": { "@remotion/paths": "4.0.486", "remotion": "4.0.486" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-k+2EiXVd6heKVnGACfSnFF5Ox46lvsq6RFtHvPc3CunSMJa04Ui1Z4vNQDcddCSXoHnT/+vht6bjZNlSk36OrQ=="],
334
+ "@remotion/shapes": ["@remotion/shapes@4.0.507", "", { "dependencies": { "@remotion/paths": "4.0.507", "remotion": "4.0.507" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-PinB0Va4FRdv9OeaoIxikPqius9XQj5S5xruQEg/Y1CX+1Eu9KOVXqPOrMgELjkfPLLQ0rA/I4gmKoVxhaoYDQ=="],
335
335
 
336
- "@remotion/transitions": ["@remotion/transitions@4.0.486", "", { "dependencies": { "@remotion/paths": "4.0.486", "@remotion/shapes": "4.0.486", "remotion": "4.0.486" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-U7K21B2NsPie3AE4vrbX7Th46Nw1dFvjs5smYLjuYRaSWPO6vTbNbPQn+pQFXf/dzTjygTNUU9Co5s3oZZNz9A=="],
336
+ "@remotion/transitions": ["@remotion/transitions@4.0.507", "", { "dependencies": { "@remotion/paths": "4.0.507", "@remotion/shapes": "4.0.507", "remotion": "4.0.507" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-d0rtF2gVdSxR4Q35kjSSEdQ1ji0WoKFRaeoNgx/7NO13c/3gDSECAg9m2LoP+fN5pkZFdfL8z+yhM16rL7rWjg=="],
337
337
 
338
- "@remotion/web-renderer": ["@remotion/web-renderer@4.0.486", "", { "dependencies": { "@mediabunny/aac-encoder": "1.50.3", "@mediabunny/flac-encoder": "1.50.3", "@mediabunny/mp3-encoder": "1.50.3", "@remotion/licensing": "4.0.486", "mediabunny": "1.50.3", "remotion": "4.0.486" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-RbG4qFmg6paFDDc/rNL/3hooDPEWPnHu8MtDJB5axD/TiXFsaNR009L9dP610b9hPGwlykIOooSlD7INzo0Gqw=="],
338
+ "@remotion/web-renderer": ["@remotion/web-renderer@4.0.507", "", { "dependencies": { "@mediabunny/aac-encoder": "1.50.8", "@mediabunny/flac-encoder": "1.50.8", "@mediabunny/mp3-encoder": "1.50.8", "@remotion/licensing": "4.0.507", "mediabunny": "1.50.8", "remotion": "4.0.507" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-fH8iZX1e0hmeJ+hPqn3RfqrDBNxMGsZZzfdyX2QpTDZ9F2kKqOkh0dQsRqaU8o+YoE8JNV/+FyYm4yyDAh9o6Q=="],
339
339
 
340
340
  "@stablelib/base64": ["@stablelib/base64@1.0.1", "", {}, "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="],
341
341
 
@@ -757,7 +757,7 @@
757
757
 
758
758
  "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
759
759
 
760
- "remotion": ["remotion@4.0.486", "", { "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-njrfAmfr+fJCr4NXY6hlbzWpIRkfnF0lPdRGZ4DkQao3/XRUtuB166GcVuaVnUpwa8NP+W+Q6TczOPveUpr0pw=="],
760
+ "remotion": ["remotion@4.0.507", "", { "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-g/NrnRZKJyh0isE+/0sIxKlgJl6BH5v/CJ35W4TGYR1obQZf60fHkf9CGxSBHJEw27dG1PJCgKT75YGfoCZ7tg=="],
761
761
 
762
762
  "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
763
763
 
@@ -881,17 +881,11 @@
881
881
 
882
882
  "@pdf-lib/upng/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
883
883
 
884
- "@remotion/media/mediabunny": ["mediabunny@1.50.3", "", { "dependencies": { "@types/dom-mediacapture-transform": "^0.1.11", "@types/dom-webcodecs": "0.1.13" } }, "sha512-lw6NjC6++/p6MzHC3u7RbkpM0dnukEQcAAXzZwXlmmx9y8n6132DXUMer2WaNn8RPuGgUpnemi/QIVNpEnONhQ=="],
884
+ "@remotion/web-renderer/@mediabunny/aac-encoder": ["@mediabunny/aac-encoder@1.50.8", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-A5Se/LZd6RmYq/h36lBMSEsHvsyW8d0toR7FrAwpsFYbK+DVQYf90KiBT1Aw/mzLXx8/ypIOORJnd1sVZqOvJQ=="],
885
885
 
886
- "@remotion/media/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
886
+ "@remotion/web-renderer/@mediabunny/flac-encoder": ["@mediabunny/flac-encoder@1.50.8", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-4cfN03SbEoQaG+eBeYFUAb1R1ALaAHzf43dXFzQTr/oO5ueqzTgBoG3coKrIvBaAMU7Vv36mrBKLX8bvTppdAQ=="],
887
887
 
888
- "@remotion/web-renderer/@mediabunny/aac-encoder": ["@mediabunny/aac-encoder@1.50.3", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-rVPnhJbn+7BUsu09sEl/bee/DkzmAc33oErJ250RPlCqrPndx61DSb2GLvVmmSx6L2LU2WyCnvJIMUrGPJEXPA=="],
889
-
890
- "@remotion/web-renderer/@mediabunny/flac-encoder": ["@mediabunny/flac-encoder@1.50.3", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-aIEHE3eQ+hG35rRvtNNYO6ufTiGkZpzgOBjcqK8CQuRAMtUY9/DzV12jj52L0nSj2zmBKXQQ6dWBWsusigV/dQ=="],
891
-
892
- "@remotion/web-renderer/@mediabunny/mp3-encoder": ["@mediabunny/mp3-encoder@1.50.3", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-dyjWOhG27+ctlejfjY4vFy0g8wzo0NCRTNQ/GNsCaHxWkduaq0GI2BWKXm6UFCsfnUKgtDWMEu4zOPl9fKpbsA=="],
893
-
894
- "@remotion/web-renderer/mediabunny": ["mediabunny@1.50.3", "", { "dependencies": { "@types/dom-mediacapture-transform": "^0.1.11", "@types/dom-webcodecs": "0.1.13" } }, "sha512-lw6NjC6++/p6MzHC3u7RbkpM0dnukEQcAAXzZwXlmmx9y8n6132DXUMer2WaNn8RPuGgUpnemi/QIVNpEnONhQ=="],
888
+ "@remotion/web-renderer/@mediabunny/mp3-encoder": ["@mediabunny/mp3-encoder@1.50.8", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-eBT/H30tTu8AmZXqZ5RTpJ9VmwVlwednajuOrrWp0GS6cbGXsGMQ60Qvr3ZMIE0QDiadlEzb8/ozR+doP+MC1g=="],
895
889
 
896
890
  "body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
897
891
 
@@ -1641,7 +1641,21 @@ const CLIP_TAGS = new Set(['Sequence', 'Series.Sequence', 'TransitionSeries.Sequ
1641
1641
  /** Transition tags (occupy a slot between clips inside a TransitionSeries). */
1642
1642
  const TRANSITION_TAGS = new Set(['Series.Transition', 'TransitionSeries.Transition']);
1643
1643
  /** Media tags that carry a `src` (the replace-media + drop targets). */
1644
- const MEDIA_TAGS = new Set(['Video', 'OffthreadVideo', 'Audio', 'Img', 'Image']);
1644
+ // `Maude*` spellings are the collision-avoidance ALIASES clip-ops emits when a
1645
+ // canvas already declares a component of that name (e.g. `export default
1646
+ // function Video()`). They are the same elements and must tokenize the same,
1647
+ // or the Timeline silently loses the clip's media (RCA
1648
+ // issue-mp4-audio-export-html5audio-silent-degrade follow-up).
1649
+ const MEDIA_TAGS = new Set([
1650
+ 'Video',
1651
+ 'OffthreadVideo',
1652
+ 'Audio',
1653
+ 'Img',
1654
+ 'Image',
1655
+ 'MaudeVideo',
1656
+ 'MaudeAudio',
1657
+ 'MaudeImg',
1658
+ ]);
1645
1659
  /** Remotion/layout primitives that are structural, not their own timeline layer. */
1646
1660
  const LAYER_SKIP_TAGS = new Set([
1647
1661
  'AbsoluteFill',
@@ -4534,7 +4548,7 @@ export function assembleCompSource(
4534
4548
  const dur = Math.max(1, Math.round(c.durationInFrames ?? defDur));
4535
4549
  seqLines.push(
4536
4550
  ` <TransitionSeries.Sequence name="clip-${i + 1}" durationInFrames={${dur}}>`,
4537
- ` <OffthreadVideo src="${escapeAttr(c.src)}" />`,
4551
+ ` <Video src="${escapeAttr(c.src)}" />`,
4538
4552
  ` </TransitionSeries.Sequence>`
4539
4553
  );
4540
4554
  cursor += dur;
@@ -4557,16 +4571,26 @@ export function assembleCompSource(
4557
4571
  // the cursor (with the historical defDur floor for degenerate inputs).
4558
4572
  const total = videos.length || audios.length ? Math.max(cursor, defDur) : fps * 5;
4559
4573
 
4574
+ // Media elements come from @remotion/media, NEVER from 'remotion'. The export's
4575
+ // only audio-capable path (renderMediaOnWeb) rejects `remotion`'s `Audio`
4576
+ // (= Html5Audio) and `OffthreadVideo` outright, so a reel assembled with those
4577
+ // exports MUTED — and ~40x slower, on the frame-step fallback. Every reel this
4578
+ // function emits has a music bed by default, which made it the widest carrier
4579
+ // of the bug. RCA issue-mp4-audio-export-html5audio-silent-degrade.
4560
4580
  const remotionImports = ['AbsoluteFill'];
4561
- if (videos.length) remotionImports.push('OffthreadVideo');
4562
- if (audios.length) remotionImports.push('Audio');
4581
+ const mediaImports: string[] = [];
4582
+ if (videos.length) mediaImports.push('Video');
4583
+ if (audios.length) mediaImports.push('Audio');
4563
4584
  const transitionsImport = videos.length
4564
4585
  ? `import { TransitionSeries } from '@remotion/transitions';\n`
4565
4586
  : '';
4587
+ const mediaImport = mediaImports.length
4588
+ ? `import { ${mediaImports.join(', ')} } from '@remotion/media';\n`
4589
+ : '';
4566
4590
 
4567
4591
  return [
4568
4592
  `import { DesignCanvas, DCSection, DCArtboard, VideoComp } from '@maude/canvas-lib';`,
4569
- `${transitionsImport}import { ${remotionImports.join(', ')} } from 'remotion';`,
4593
+ `${transitionsImport}${mediaImport}import { ${remotionImports.join(', ')} } from 'remotion';`,
4570
4594
  ``,
4571
4595
  `const Comp = () => (`,
4572
4596
  ` <AbsoluteFill style={{ background: 'var(--bg-0)' }}>`,