@1agh/maude 1.0.5 → 1.0.7

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.
@@ -0,0 +1,53 @@
1
+ // export-assemble.test.ts — DDR-231: the in-cell half of the pptx browser
2
+ // lane. assemblePngDeck composes client-captured PNG bytes into a deck with
3
+ // PptxGenJS — pure JS over pure data (the zip containment class), so it must
4
+ // work with no browser, no filesystem inputs, no tenant checkout.
5
+
6
+ import { describe, expect, test } from 'bun:test';
7
+
8
+ import { assemblePngDeck } from '../exporters/pptx.ts';
9
+
10
+ // 1×1 transparent PNG.
11
+ const PNG_1PX = Uint8Array.from(
12
+ atob(
13
+ 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='
14
+ ),
15
+ (c) => c.charCodeAt(0)
16
+ );
17
+
18
+ describe('assemblePngDeck — bytes in, deck out', () => {
19
+ test('two PNGs become a two-slide .pptx (zip container with per-slide parts)', async () => {
20
+ const body = await assemblePngDeck([PNG_1PX, PNG_1PX], 3);
21
+ // .pptx IS a zip — PK\x03\x04 magic.
22
+ expect(body[0]).toBe(0x50);
23
+ expect(body[1]).toBe(0x4b);
24
+ const JSZip = (await import('jszip')).default;
25
+ const zip = await JSZip.loadAsync(body);
26
+ expect(zip.file('ppt/slides/slide1.xml')).toBeTruthy();
27
+ expect(zip.file('ppt/slides/slide2.xml')).toBeTruthy();
28
+ expect(zip.file('ppt/slides/slide3.xml')).toBeFalsy();
29
+ });
30
+
31
+ test('non-PNG bytes are refused, not composed into a corrupt deck', async () => {
32
+ await expect(assemblePngDeck([new TextEncoder().encode('<svg/>')], 3)).rejects.toThrow(
33
+ /not a PNG/
34
+ );
35
+ });
36
+
37
+ test('a \\x89PNG prefix on non-PNG bytes is caught by the full 8-byte signature (L3)', async () => {
38
+ // First 4 bytes match the old check, bytes 4-7 do not.
39
+ const fake = new Uint8Array(24);
40
+ fake.set([0x89, 0x50, 0x4e, 0x47, 0x00, 0x00, 0x00, 0x00], 0);
41
+ await expect(assemblePngDeck([fake], 3)).rejects.toThrow(/not a PNG/);
42
+ });
43
+
44
+ test('an implausible IHDR dimension is refused, not fed to the geometry math (F3)', async () => {
45
+ // Valid 8-byte sig + IHDR claiming 0x7fffffff × 0x7fffffff.
46
+ const bomb = new Uint8Array(24);
47
+ bomb.set([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], 0);
48
+ const dv = new DataView(bomb.buffer);
49
+ dv.setUint32(16, 0x7fffffff);
50
+ dv.setUint32(20, 0x7fffffff);
51
+ await expect(assemblePngDeck([bomb], 3)).rejects.toThrow(/implausible PNG dimensions/);
52
+ });
53
+ });
@@ -0,0 +1,126 @@
1
+ // export-browser-lane.test.ts — DDR-231 lane-resolution matrix for the client
2
+ // half of the hybrid export lanes (client/export-lane.js). Fail-first: red
3
+ // before the browser lane existed (the module didn't).
4
+
5
+ import { describe, expect, test } from 'bun:test';
6
+
7
+ import {
8
+ BROWSER_CAPTURE_FORMATS,
9
+ browserCaptureEligible,
10
+ captureScale,
11
+ MAX_CAPTURE_ITEMS,
12
+ sanitizeCapturedItems,
13
+ } from '../client/export-lane.js';
14
+
15
+ const PNG_1PX = Uint8Array.from(
16
+ atob(
17
+ 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='
18
+ ),
19
+ (c) => c.charCodeAt(0)
20
+ );
21
+ const pngBlob = () => new Blob([PNG_1PX], { type: 'image/png' });
22
+
23
+ describe('browserCaptureEligible — who captures where', () => {
24
+ const base = { format: 'png', scope: 'artboard', artboardId: 'board-1' };
25
+
26
+ test('desktop/local NEVER routes through the browser bridge (playwright spine owns it)', () => {
27
+ expect(browserCaptureEligible({ ...base, exportLane: 'local' })).toBe(false);
28
+ expect(browserCaptureEligible({ ...base, exportLane: undefined })).toBe(false);
29
+ });
30
+
31
+ test('a workspace WITH a render service still prefers the browser for png/svg artboard', () => {
32
+ expect(browserCaptureEligible({ ...base, exportLane: 'remote' })).toBe(true);
33
+ expect(browserCaptureEligible({ ...base, exportLane: 'remote', format: 'svg' })).toBe(true);
34
+ });
35
+
36
+ test('a workspace WITHOUT a render service gets png/svg artboard too (the DDR-231 win)', () => {
37
+ expect(browserCaptureEligible({ ...base, exportLane: 'none' })).toBe(true);
38
+ });
39
+
40
+ test('video formats stay on the jobs lane (Canva/Kapwing pattern — full-CSS fidelity)', () => {
41
+ for (const format of ['mp4', 'webm', 'gif', 'pdf', 'html', 'zip', 'canva']) {
42
+ expect(browserCaptureEligible({ ...base, exportLane: 'remote', format })).toBe(false);
43
+ }
44
+ });
45
+
46
+ test('the pptx deck rides the browser lane on its (only) canvas-as-separate scope', () => {
47
+ const deck = { format: 'pptx', scope: 'canvas-as-separate' };
48
+ expect(browserCaptureEligible({ ...deck, exportLane: 'remote' })).toBe(true);
49
+ expect(browserCaptureEligible({ ...deck, exportLane: 'none' })).toBe(true);
50
+ expect(browserCaptureEligible({ ...deck, exportLane: 'local' })).toBe(false);
51
+ expect(
52
+ browserCaptureEligible({ format: 'pptx', scope: 'artboard', exportLane: 'remote' })
53
+ ).toBe(false);
54
+ });
55
+
56
+ test('non-artboard scopes stay on the jobs lane (v1: capture what you are looking at)', () => {
57
+ for (const scope of ['selection', 'canvas-as-separate', 'project-raw']) {
58
+ expect(browserCaptureEligible({ ...base, exportLane: 'remote', scope })).toBe(false);
59
+ }
60
+ });
61
+
62
+ test('no known artboard → no browser capture (the bridge selects by id)', () => {
63
+ expect(browserCaptureEligible({ ...base, exportLane: 'remote', artboardId: null })).toBe(false);
64
+ });
65
+
66
+ test('the format set matches what canvas-lib can actually produce', () => {
67
+ expect([...BROWSER_CAPTURE_FORMATS].sort()).toEqual(['png', 'svg']);
68
+ });
69
+ });
70
+
71
+ describe('sanitizeCapturedItems — the untrusted-reply neutralizer (F1)', () => {
72
+ test('a forged non-PNG blob named .png is REJECTED by magic sniffing', async () => {
73
+ const evil = [
74
+ { name: 'invoice.png', blob: new Blob(['<script>alert(1)</script>'], { type: 'image/png' }) },
75
+ ];
76
+ await expect(sanitizeCapturedItems(evil, 'png')).rejects.toThrow(/not a valid png/);
77
+ });
78
+
79
+ test('a real PNG passes and gets a path-stripped name + forced MIME', async () => {
80
+ const [safe] = await sanitizeCapturedItems(
81
+ [{ name: '../../etc/passwd\r\n.png', blob: pngBlob() }],
82
+ 'png'
83
+ );
84
+ expect(safe.name).not.toContain('/');
85
+ expect(safe.name).not.toContain('..');
86
+ expect(safe.name.endsWith('.png')).toBe(true);
87
+ expect(safe.blob.type).toBe('image/png');
88
+ });
89
+
90
+ test('svg magic (<svg / <?xml) is accepted; other bytes rejected', async () => {
91
+ const [ok] = await sanitizeCapturedItems(
92
+ [{ name: 'b.svg', blob: new Blob(['<svg/>']) }],
93
+ 'svg'
94
+ );
95
+ expect(ok.name).toBe('b.svg');
96
+ expect(ok.blob.type).toBe('image/svg+xml');
97
+ await expect(
98
+ sanitizeCapturedItems([{ name: 'x.svg', blob: new Blob(['not svg']) }], 'svg')
99
+ ).rejects.toThrow(/not a valid svg/);
100
+ });
101
+
102
+ test('too many items is refused (a forged reply cannot drive N downloads)', async () => {
103
+ const many = Array.from({ length: MAX_CAPTURE_ITEMS + 1 }, () => ({ blob: pngBlob() }));
104
+ await expect(sanitizeCapturedItems(many, 'png')).rejects.toThrow(/too many items/);
105
+ });
106
+
107
+ test('a non-blob item is refused', async () => {
108
+ await expect(
109
+ sanitizeCapturedItems([{ name: 'x.png', blob: 'not a blob' as unknown as Blob }], 'png')
110
+ ).rejects.toThrow(/not a blob/);
111
+ });
112
+ });
113
+
114
+ describe('captureScale — dpi folds over scale, clamped to the raster guard window', () => {
115
+ test('scale multiplier passes through', () => {
116
+ expect(captureScale({ scale: 2 })).toBe(2);
117
+ });
118
+ test('dpi wins over scale (mirrors exporters/png.ts resolveDeviceScale)', () => {
119
+ expect(captureScale({ dpi: 300, scale: 2 })).toBe(300 / 96);
120
+ });
121
+ test('clamped to [1, 8]', () => {
122
+ expect(captureScale({ dpi: 1200 })).toBe(8);
123
+ expect(captureScale({ scale: 0 })).toBe(1);
124
+ expect(captureScale()).toBe(1);
125
+ });
126
+ });
@@ -0,0 +1,146 @@
1
+ // export-capture-fidelity.test.ts — DDR-231 T6: the fidelity gate for the
2
+ // browser export lane.
3
+ //
4
+ // The browser lane rasterizes dom-to-svg output (capture-core.ts); the
5
+ // reference spine screenshots the live element (the playwright path the
6
+ // desktop + render worker use). dom-to-svg RECONSTRUCTS the DOM as SVG
7
+ // primitives, so the two are not byte-identical — this gate pins how far
8
+ // apart they may drift on a representative artboard (tokens-styled text,
9
+ // gradient, border-radius, shadow, nested layout). A capture regression
10
+ // (dropped background, missing text, wrong geometry) blows well past these
11
+ // thresholds; anti-aliasing and shadow-softness differences do not.
12
+ //
13
+ // Runs a real Chromium (repo playwright) — lives in the studio suite, not the
14
+ // sync lane.
15
+
16
+ import { describe, expect, test } from 'bun:test';
17
+
18
+ import { getBrowserBundle, getCaptureCoreBundle } from '../exporters/_browser-bundles.ts';
19
+
20
+ const FIXTURE = `<!doctype html><html><body style="margin:0;background:#222">
21
+ <div class="dc-world">
22
+ <div data-dc-screen="board-1" style="position:absolute;left:0;top:0;width:420px;height:300px;
23
+ background:linear-gradient(135deg,#1c2340,#3a1f47);border-radius:16px;overflow:hidden;
24
+ font-family:Arial,Helvetica,sans-serif;color:#f5f2ea">
25
+ <div style="padding:28px">
26
+ <div style="font-size:24px;font-weight:700;letter-spacing:-0.4px">Summer Camp</div>
27
+ <div style="font-size:13px;opacity:.75;margin-top:6px">Brno · 12–16 August</div>
28
+ <div style="display:flex;gap:10px;margin-top:18px">
29
+ <div style="flex:1;height:64px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15);border-radius:10px"></div>
30
+ <div style="flex:1;height:64px;background:#e4572e;border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.4)"></div>
31
+ </div>
32
+ <div style="margin-top:16px;font-size:12px;line-height:1.5;opacity:.85">
33
+ Registrace do konce července. Kapacita 60 hráčů.
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </div></body></html>`;
38
+
39
+ describe('browser-lane capture vs playwright reference', () => {
40
+ test(
41
+ 'raster stays within the drift thresholds on a representative artboard',
42
+ async () => {
43
+ const [iife, core] = await Promise.all([
44
+ getBrowserBundle('dom-to-svg', 'domToSvg'),
45
+ getCaptureCoreBundle(),
46
+ ]);
47
+ const { launchChromium } = (await import('../bin/_pw-launch.mjs')) as {
48
+ launchChromium: () => Promise<{
49
+ newContext: (o: unknown) => Promise<{ newPage: () => Promise<unknown> }>;
50
+ close: () => Promise<void>;
51
+ }>;
52
+ };
53
+ const browser = await launchChromium();
54
+ try {
55
+ const ctx = await browser.newContext({
56
+ viewport: { width: 800, height: 600 },
57
+ deviceScaleFactor: 1,
58
+ });
59
+ // biome-ignore lint/suspicious/noExplicitAny: playwright page surface
60
+ const page: any = await ctx.newPage();
61
+ await page.setContent(FIXTURE);
62
+ await page.evaluate(() => document.fonts.ready);
63
+ await page.addScriptTag({ path: iife });
64
+ await page.addScriptTag({ path: core, type: 'module' });
65
+ await page.waitForFunction(
66
+ () => !!(window as never as Record<string, unknown>)['__maudeCaptureCore']
67
+ );
68
+
69
+ // Reference: the playwright spine's capture of the same element.
70
+ const el = page.locator('[data-dc-screen]');
71
+ const refBytes: Uint8Array = await el.screenshot();
72
+ const refB64 = Buffer.from(refBytes).toString('base64');
73
+
74
+ const verdict = await page.evaluate(async (refDataUri: string) => {
75
+ // biome-ignore lint/suspicious/noExplicitAny: injected globals
76
+ const w = window as any;
77
+ const core = w.__maudeCaptureCore;
78
+ const target = document.querySelector('[data-dc-screen]') as Element;
79
+ const svg = await core.svgForElement(target, w.domToSvg);
80
+ const blob: Blob = await core.rasterizeSvg(svg, 1);
81
+
82
+ const load = (src: string) =>
83
+ new Promise<HTMLImageElement>((resolve, reject) => {
84
+ const img = new Image();
85
+ img.onload = () => resolve(img);
86
+ img.onerror = () => reject(new Error('decode failed'));
87
+ img.src = src;
88
+ });
89
+ const candUrl = URL.createObjectURL(blob);
90
+ const [ref, cand] = await Promise.all([load(refDataUri), load(candUrl)]);
91
+ const W = ref.naturalWidth;
92
+ const H = ref.naturalHeight;
93
+ const draw = (img: HTMLImageElement) => {
94
+ const c = document.createElement('canvas');
95
+ c.width = W;
96
+ c.height = H;
97
+ const g = c.getContext('2d') as CanvasRenderingContext2D;
98
+ g.drawImage(img, 0, 0, W, H);
99
+ return g.getImageData(0, 0, W, H).data;
100
+ };
101
+ const a = draw(ref);
102
+ const b = draw(cand);
103
+ let sum = 0;
104
+ let close = 0;
105
+ const n = W * H;
106
+ for (let i = 0; i < n * 4; i += 4) {
107
+ const d =
108
+ (Math.abs(a[i] - b[i]) +
109
+ Math.abs(a[i + 1] - b[i + 1]) +
110
+ Math.abs(a[i + 2] - b[i + 2])) /
111
+ 3;
112
+ sum += d;
113
+ if (d <= 32) close += 1;
114
+ }
115
+ URL.revokeObjectURL(candUrl);
116
+ return {
117
+ refW: W,
118
+ refH: H,
119
+ candW: cand.naturalWidth,
120
+ candH: cand.naturalHeight,
121
+ meanDelta: sum / n,
122
+ closeRatio: close / n,
123
+ };
124
+ }, `data:image/png;base64,${refB64}`);
125
+
126
+ // Geometry must agree exactly — a size mismatch is a broken capture,
127
+ // not drift.
128
+ expect(verdict.candW).toBe(verdict.refW);
129
+ expect(verdict.candH).toBe(verdict.refH);
130
+ // Thresholds set from the MEASURED baseline (fail-first evidence,
131
+ // 2026-08-23): healthy capture = meanDelta 1.48, closeRatio 1.0;
132
+ // a simulated dropped-text regression = meanDelta 5.37, closeRatio
133
+ // 0.974. Both captures render in the SAME engine, so platform font
134
+ // variance cancels — the residual is dom-to-svg reconstruction only.
135
+ // meanDelta < 4 / closeRatio > 0.99 keeps ~2.7× headroom over healthy
136
+ // while catching the text-drop class (mean-only thresholds missed it —
137
+ // text is a small pixel fraction, hence the paired closeRatio gate).
138
+ expect(verdict.meanDelta).toBeLessThan(4);
139
+ expect(verdict.closeRatio).toBeGreaterThan(0.99);
140
+ } finally {
141
+ await browser.close();
142
+ }
143
+ },
144
+ { timeout: 60_000 }
145
+ );
146
+ });
@@ -59,3 +59,21 @@ describe('lazy-bundle guarantee — <PhotoLayer> keeps pixi out of the eager gra
59
59
  expect(js.includes('isDefaultEdit') || js.includes('PhotoLayer')).toBe(true);
60
60
  });
61
61
  });
62
+
63
+ describe('lazy-bundle guarantee — the export-capture bridge keeps dom-to-svg out of the eager graph', () => {
64
+ // DDR-231: every canvas mounts useExportCaptureBridge (via DesignCanvas), so
65
+ // an EAGER dom-to-svg import would tax every canvas load with the ~110 KB
66
+ // bundle. Same fragility as pixi: canvas builds run `splitting: false`, and
67
+ // a refactor that turns the bridge's `await import('dom-to-svg')` static
68
+ // silently re-breaks this.
69
+ test('a plain canvas has NO eager `from "dom-to-svg"` import', async () => {
70
+ const js = await buildCanvas(CANVAS);
71
+ const eager = [...js.matchAll(/import\s[^;]*?from\s*["']dom-to-svg["']/g)];
72
+ expect(eager.length).toBe(0);
73
+ });
74
+
75
+ test('dom-to-svg is reachable only via a lazy runtime import("dom-to-svg")', async () => {
76
+ const js = await buildCanvas(CANVAS);
77
+ expect(/import\(\s*["']dom-to-svg["']\s*\)/.test(js)).toBe(true);
78
+ });
79
+ });
@@ -327,14 +327,22 @@ describe('pruneForWorkspace — how a cell can boot at all', () => {
327
327
  '/_api/export-jobs': 'j',
328
328
  '/_api/export-jobs/download': 'd',
329
329
  '/_api/export-history': 'h',
330
+ // DDR-231 — the browser lane's assemble half: pure data (client-captured
331
+ // PNGs) → PptxGenJS in-cell; nothing evaluates, same class as zip.
332
+ '/_api/export-assemble': 'a',
333
+ // DDR-231 T7 — warm-up ping: proxies GET /_health to the render service
334
+ // so the container wakes while the member is still picking options.
335
+ '/_api/export-warmup': 'w',
330
336
  '/_api/export-next-phase-surprise': 'f',
331
337
  '/_health': 'ok',
332
338
  };
333
339
  const { routes, removed } = pruneForWorkspace(table);
334
340
  expect(Object.keys(routes).sort()).toEqual([
341
+ '/_api/export-assemble',
335
342
  '/_api/export-history',
336
343
  '/_api/export-jobs',
337
344
  '/_api/export-jobs/download',
345
+ '/_api/export-warmup',
338
346
  '/_health',
339
347
  ]);
340
348
  expect(removed).toEqual(['/_api/export', '/_api/export-next-phase-surprise']);
@@ -1,6 +1,24 @@
1
1
  {
2
2
  "$schema": "./whats-new.schema.json",
3
3
  "entries": [
4
+ {
5
+ "id": "instant-exports-in-your-browser",
6
+ "version": "1.0.7",
7
+ "date": "2026-08-23",
8
+ "kind": "feature",
9
+ "title": "Instant exports, right in your browser",
10
+ "summary": "In a hosted workspace, PNG and SVG of the artboard you're looking at — and the whole PPTX deck — now export straight from your browser in seconds. No render service, no waiting for a container to wake up. Video and PDF still render on the service, which now warms up the moment you open the export dialog.",
11
+ "surface": "design-ui"
12
+ },
13
+ {
14
+ "id": "cloud-exports-actually-arrive",
15
+ "version": "1.0.6",
16
+ "date": "2026-08-23",
17
+ "kind": "fix",
18
+ "title": "Cloud exports actually arrive",
19
+ "summary": "Exporting from a hosted or self-hosted workspace no longer times out after a long wait. The render service was mangling the page it captured before it ever loaded; that's fixed, so PNG, PDF, PPTX and video exports now finish and download like they should.",
20
+ "surface": "design-ui"
21
+ },
4
22
  {
5
23
  "id": "invite-links-open-a-welcome-page",
6
24
  "version": "1.0.4",
@@ -98,8 +98,21 @@ export const FORBIDDEN_ROUTE_PREFIXES: ReadonlyArray<{
98
98
  // The job lane: enqueue / list+stream / history. No evaluation happens
99
99
  // in-cell — browser formats dispatch to maude-render (exporters/jobs.ts
100
100
  // render lane), zip runs browser-free, and lane `none` refuses with a
101
- // remedy. DDR amending DDR-209 A′1 records the contract.
102
- except: ['/_api/export-jobs', '/_api/export-jobs/download', '/_api/export-history'],
101
+ // remedy. DDR-230 (amending DDR-209 A′1) records the contract.
102
+ // `/_api/export-assemble` (DDR-231): the browser lane's assemble half —
103
+ // the MEMBER's browser captured the artboard PNGs, the cell composes the
104
+ // deck with PptxGenJS (pure JS over pure data, the zip containment
105
+ // class). Nothing evaluates here either.
106
+ // `/_api/export-warmup` (DDR-231 T7): proxies GET /_health to the render
107
+ // service so the container wakes while the member picks export options —
108
+ // no job body, no evaluation, no secret in the response.
109
+ except: [
110
+ '/_api/export-jobs',
111
+ '/_api/export-jobs/download',
112
+ '/_api/export-history',
113
+ '/_api/export-assemble',
114
+ '/_api/export-warmup',
115
+ ],
103
116
  },
104
117
  {
105
118
  prefix: '/_api/generate',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1agh/maude",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Marketplace of Claude Code plugins by Michal Dovrtěl: `design` (canvas-first design iteration) + `flow` (generic agentic workflow loop with .ai second brain). Ships the `maude` CLI (with `mdcc` legacy alias) to scaffold workspace, run the design dev server, and manage configs.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -54,13 +54,13 @@
54
54
  "test:e2e:desktop:parity:cloud": "pnpm --filter @maude/desktop-e2e e2e:parity:cloud"
55
55
  },
56
56
  "optionalDependencies": {
57
- "@1agh/maude-darwin-arm64": "1.0.5",
58
- "@1agh/maude-darwin-x64": "1.0.5",
59
- "@1agh/maude-linux-arm64": "1.0.5",
60
- "@1agh/maude-linux-arm64-musl": "1.0.5",
61
- "@1agh/maude-linux-x64": "1.0.5",
62
- "@1agh/maude-linux-x64-musl": "1.0.5",
63
- "@1agh/maude-win32-x64": "1.0.5"
57
+ "@1agh/maude-darwin-arm64": "1.0.7",
58
+ "@1agh/maude-darwin-x64": "1.0.7",
59
+ "@1agh/maude-linux-arm64": "1.0.7",
60
+ "@1agh/maude-linux-arm64-musl": "1.0.7",
61
+ "@1agh/maude-linux-x64": "1.0.7",
62
+ "@1agh/maude-linux-x64-musl": "1.0.7",
63
+ "@1agh/maude-win32-x64": "1.0.7"
64
64
  },
65
65
  "files": [
66
66
  "cli",
@@ -156,7 +156,8 @@
156
156
  "@remotion/transitions/wipe": "./_canvas-runtime/@remotion_transitions_wipe.js",
157
157
  "@remotion/transitions/flip": "./_canvas-runtime/@remotion_transitions_flip.js",
158
158
  "@remotion/transitions/clock-wipe": "./_canvas-runtime/@remotion_transitions_clock-wipe.js",
159
- "@remotion/transitions/none": "./_canvas-runtime/@remotion_transitions_none.js"
159
+ "@remotion/transitions/none": "./_canvas-runtime/@remotion_transitions_none.js",
160
+ "dom-to-svg": "./_canvas-runtime/dom-to-svg.js"
160
161
  }
161
162
  }
162
163
  </script>