browser-tsx-sandbox 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,428 @@
1
+ Вот полная, подробная архитектурная документация (в формате `README.md`) для вашего проекта. Она полностью опирается на вашу Mermaid-диаграмму и детально описывает процесс динамической загрузки внешних библиотек из NPM прямо в браузере.
2
+
3
+ ---
4
+
5
+ # 📦 browser-tsx-sandbox (Pro Edition)
6
+
7
+ **Полностью автономная In-Browser среда (Pure Client-Side App) для компиляции TSX, рендеринга видео через Remotion и динамической загрузки NPM-зависимостей.**
8
+
9
+ Эта архитектура позволяет создавать видеоредакторы и AI-генераторы видео, работающие на 100% в браузере пользователя, без использования Node.js бэкенда для сборки бандлов.
10
+
11
+ ## ✨ Ключевые возможности
12
+
13
+ - 🚀 **Zero-Backend:** Вся работа с файлами, компиляция кода и рендеринг происходят на клиенте.
14
+ - 📦 **Нативная поддержка NPM:** Динамический импорт любых библиотек (например, `framer-motion`, `d3`, `three.js`) напрямую с CDN (esm.sh / jsdelivr).
15
+ - 🎨 **Remotion + Tailwind:** Мгновенный рендеринг видеокадров и генерация служебных CSS-классов на лету (Tailwind JIT).
16
+ - 🧩 **Локальные ассеты:** Поддержка Drag & Drop медиафайлов через `Blob API` и `URL.createObjectURL` без загрузки на сервер.
17
+ - 🛡️ **Изоляция и Безопасность:** Безопасное выполнение сгенерированного ИИ кода (Shadowing глобальных переменных).
18
+ - 🪄 **Интеграция Lucide Icons:** Встроенный адаптер для поиска и рендеринга иконок без загрузки всей библиотеки целиком.
19
+
20
+ ---
21
+
22
+ ## 🚀 Быстрый старт (разработка)
23
+
24
+ Это **библиотечный пакет**, а не готовое приложение, поэтому у него нет `dev`-сервера — разработка идёт через тесты и проверку типов.
25
+
26
+ ### 1. Требования
27
+ - Node.js 18+ (проект проверен на Node 24)
28
+ - npm
29
+
30
+ ### 2. Установка зависимостей
31
+ ```bash
32
+ npm install
33
+ ```
34
+
35
+ ### 3. Команды
36
+
37
+ | Команда | Что делает |
38
+ |---|---|
39
+ | `npm test` | один прогон всех тестов (Vitest) |
40
+ | `npm run test:watch` | тесты в режиме наблюдения (watch) |
41
+ | `npm run test:e2e` | e2e-тест реального рендера видео (Remotion + Chrome) |
42
+ | `npm run render` | вручную отрендерить PNG-кадр и MP4 из сцены песочницы |
43
+ | `npm run render:assets` | то же, но с медиа-ассетами, распакованными из ZIP |
44
+ | `npm run render:example` | рендер реальной анимации из `examples/remotion-scene.tsx` |
45
+ | `npm run render:widgets` | рендер виджетов из JSON-каталога `examples/vidora-widgets.json` |
46
+ | `npm run render:widgets:logo` | рендер каталога `examples/vidora-widgets-logo.json` |
47
+ | `npm run render:props` | рендер вариаций пропсов (проверка, что параметры меняют результат) |
48
+ | `npm run build` | сборка npm-пакета (`tsup` → `dist/`: ESM + CJS + типы) |
49
+ | `npm run pack:check` | `npm pack --dry-run` — показать содержимое тарбола |
50
+ | `npm run verify:video` | проверить выданный MP4 (контейнер, кодек, размеры, длительность) |
51
+ | `npm run typecheck` | статическая проверка типов (`tsc --noEmit`) |
52
+
53
+ ### 4. Ручная проверка пайплайна
54
+
55
+ Песочницу можно прогнать на реальном файле `examples/remotion-scene.tsx`. Он компилируется из TSX в CommonJS, зависимости (`react`, `remotion`, `lucide-react`) подставляются заглушками, после чего сцена выполняется как React-компонент. Именно это проверяет интеграционный тест `src/facade.remotion.test.ts`:
56
+
57
+ ```bash
58
+ npx vitest run src/facade.remotion.test.ts
59
+ ```
60
+
61
+ ### 5. Реальный рендер видео (Remotion)
62
+
63
+ Папка `render/` содержит настоящий e2e-пайплайн: сцена на TSX компилируется и выполняется через `SandboxFacade`, регистрируется как Remotion-композиция и рендерится в **PNG-кадр и MP4** через headless Chrome.
64
+
65
+ ```bash
66
+ npm run render # -> render/out/frame-30.png и render/out/sandbox.mp4
67
+ npm run test:e2e # то же самое, но с проверками (размеры, наличие файлов)
68
+ ```
69
+
70
+ Требуется установленный Google Chrome (или Edge). Путь ищется автоматически; при необходимости задайте свой:
71
+
72
+ ```bash
73
+ $env:REMOTION_BROWSER="C:\путь\к\chrome.exe"; npm run render
74
+ ```
75
+
76
+ > E2E-тест намеренно не входит в `npm test`: он бандлит Remotion-проект и запускает браузер, поэтому выполняется отдельно (~1 мин).
77
+
78
+ ### 6. Ассеты из ZIP
79
+
80
+ Медиа-ресурсы (видео, картинки, шрифты, JSON) можно передать одним архивом. Библиотечная часть — `src/assets/zip.ts`: `extractAssetZip` распаковывает `Uint8Array` в карту байтов, `createAssetUrlMap` превращает её в `filename -> blob:url` для `SandboxFacade.setAssets`.
81
+
82
+ ```ts
83
+ import { extractAssetZip, createAssetUrlMap, releaseAssetUrls } from 'browser-tsx-sandbox';
84
+
85
+ const archive = extractAssetZip(zipBytes); // { 'assets/clip.mp4': Uint8Array, ... }
86
+ const urls = createAssetUrlMap(archive, (bytes) => URL.createObjectURL(new Blob([bytes])));
87
+ facade.setAssets(urls); // теперь staticFile('clip.mp4') отдаёт blob:-ссылку
88
+ // ...
89
+ releaseAssetUrls(urls, URL.revokeObjectURL);
90
+ ```
91
+
92
+ E2E-тест `render/assets.e2e.test.ts` кладёт реальный MP4 и SVG в ZIP, распаковывает их в `public` и рендерит сцену с `OffthreadVideo` + `Img` через Remotion `staticFile`:
93
+
94
+ ```bash
95
+ npm run render:assets # -> render/out/asset-still.png, render/out/asset-video.mp4
96
+ npm run test:e2e # оба e2e-сценария (без ассетов и с ассетами)
97
+ ```
98
+
99
+ ### 7. Рендер анимации из `examples/`
100
+
101
+ `npm run render:example` берёт настоящий `examples/remotion-scene.tsx` (1177 кадров, 1920×1080) и проводит его через полный пайплайн:
102
+
103
+ 1. находит в TSX абсолютные пути к видео и заменяет их на раздаваемые через `staticFile` (роль `ImportResolver`), копируя ролики в `public/b-roll/`;
104
+ 2. компилирует Tailwind-утилиты из исходника на этапе сборки и инжектит их в сцену;
105
+ 3. компилирует и выполняет сцену песочницей с реальными `react`, `remotion`, `lucide-react`;
106
+ 4. рендерит по одному ключевому кадру каждого фрагмента и короткий клип.
107
+
108
+ ```bash
109
+ npm run render:example
110
+ # -> render/out/example-frame-0075.png ... example-frame-1080.png
111
+ # -> render/out/example-map.mp4
112
+ ```
113
+
114
+ E2E-тест: `render/example.e2e.test.ts`.
115
+
116
+ ### 8. Виджеты из JSON-каталога
117
+
118
+ `examples/vidora-widgets.json` — экспорт каталога виджетов (`Vidora Motion Studio`): метаданные, `default_props` и встроенный `tsx_code`. `npm run render:widgets` парсит JSON и прогоняет **каждый** `tsx_code` через песочницу:
119
+
120
+ 1. Tailwind-утилиты компилируются из `tsx_code` всех виджетов;
121
+ 2. в Remotion-проекте каждый виджет компилируется (`compileTsx`), выполняется (`executeComponent`) и регистрируется как отдельная `Composition` с `default_props` (16:9 → 1920×1080, 9:16 → 1080×1920);
122
+ 3. рендерятся ключевые кадры каждого виджета и короткий клип.
123
+
124
+ ```bash
125
+ npm run render:widgets
126
+ # -> render/out/widget-WordByWordText16x9-*.png
127
+ # -> render/out/widget-WordByWordText9x16-*.png
128
+ # -> render/out/widget-WordByWordText16x9.mp4
129
+ ```
130
+
131
+ E2E-тест: `render/widgets.e2e.test.ts`.
132
+
133
+ Второй каталог — `examples/vidora-widgets-logo.json` (Logo Shine Badge). Пропсы влияют на рендер, а `imageUrl` разбирается и встраивается как изображение. Проверяется тестом `render/props.e2e.test.ts`:
134
+
135
+ ```bash
136
+ npm run render:widgets:logo # дефолтные пропсы
137
+ npm run render:props # вариации: текст/размер/цвет/иконка/картинка
138
+ ```
139
+
140
+ > Полное описание всех контрактов и сигнатур — в [`API.md`](./API.md).
141
+
142
+ ### 9. Пример использования на стороне потребителя
143
+
144
+ ```tsx
145
+ import { SandboxFacade } from 'browser-tsx-sandbox';
146
+ import * as React from 'react';
147
+
148
+ const facade = new SandboxFacade({ react: React });
149
+ facade.setAssets({ 'logo.png': 'blob:http://localhost/...' });
150
+
151
+ const { component: Component, error } = await facade.compile(userTsx);
152
+ ```
153
+
154
+ ---
155
+
156
+ ## 🏗 Архитектура системы
157
+
158
+ Ниже представлена полная схема взаимодействия подсистем (UI, State, Sandbox, Library Manager и Rendering Engine).
159
+
160
+ ```mermaid
161
+ graph LR
162
+ classDef ui fill:#2b6cb0,stroke:#3182ce,stroke-width:2px,color:#fff;
163
+ classDef state fill:#38a169,stroke:#48bb78,stroke-width:2px,color:#fff;
164
+ classDef core fill:#805ad5,stroke:#9f7aea,stroke-width:2px,color:#fff;
165
+ classDef frontend fill:#dd6b20,stroke:#c05621,stroke-width:2px,color:#fff;
166
+ classDef api fill:#319795,stroke:#4fd1c5,stroke-width:2px,color:#fff;
167
+ classDef lib fill:#d69e2e,stroke:#ecc94b,stroke-width:2px,color:#fff;
168
+ classDef error fill:#e53e3e,stroke:#f56565,stroke-width:2px,color:#fff;
169
+
170
+ subgraph Browser ["Вкладка браузера (Pure Client-Side App)"]
171
+
172
+ subgraph UI ["UI Layer"]
173
+ CodeEditor["CodeEditor <br/> Monaco/CodeMirror"]:::ui
174
+ PromptPanel["AI Prompt Panel <br/> generate/fix component"]:::ui
175
+ SettingsEditor["Settings Editor <br/> fps, duration, theme"]:::ui
176
+ AssetPanel["Asset Manager <br/> drag and drop, list, preview"]:::ui
177
+ LibraryManager["Library Manager <br/> add/remove libraries"]:::ui
178
+ IconPicker["Lucide Icon Picker <br/> search and insert icon"]:::ui
179
+ StatusBar["Status Bar <br/> compiling, ready, error"]:::ui
180
+ end
181
+
182
+ subgraph State ["Application State"]
183
+ ProjectStore["ProjectStore <br/> files, activeFile, code"]:::state
184
+ SettingsStore["SettingsStore <br/> fps, duration, theme, props"]:::state
185
+ AssetStore["AssetStore <br/> blobUrl, mime, size, map"]:::state
186
+ LibraryStore["LibraryStore <br/> installed libs, versions, enabled"]:::state
187
+ LucideCatalog["LucideCatalog <br/> icon names, tags"]:::state
188
+ SandboxState["SandboxState <br/> compile status, error"]:::state
189
+ end
190
+
191
+ subgraph Sandbox ["browser-tsx-sandbox"]
192
+ SandboxFacade["SandboxFacade <br/> orchestrate compile and evaluate"]:::core
193
+ ImportResolver["ImportResolver <br/> resolve local files, assets, bare imports"]:::core
194
+ SyntaxChecker["SyntaxChecker <br/> parse TSX AST"]:::core
195
+ Compiler["SucraseCompiler <br/> TSX to CommonJS"]:::core
196
+ ModuleCache["ModuleCache <br/> compiled module registry"]:::core
197
+ DependencyContainer["DependencyContainer <br/> React, Remotion, helpers"]:::core
198
+ ScopeFactory["ScopeFactory <br/> DI, allowed globals, shadowing"]:::core
199
+ RuntimeEvaluator["RuntimeEvaluator <br/> new Function execution"]:::core
200
+ ErrorMapper["ErrorMapper <br/> map errors to editor position"]:::error
201
+ end
202
+
203
+ subgraph LibSystem ["Library Runtime"]
204
+ LibraryResolver["LibraryResolver <br/> resolve bare imports"]:::lib
205
+ LibraryLoader["LibraryLoader <br/> CDN, local file, blob"]:::lib
206
+ ModuleFormatAdapter["ModuleFormatAdapter <br/> ESM/CJS/global normalize"]:::lib
207
+ LibraryStyleLoader["LibraryStyleLoader <br/> css from libraries"]:::lib
208
+ LibraryRegistry["LibraryRegistry <br/> registered runtime modules"]:::lib
209
+ LucideAdapter["LucideAdapter <br/> lucide-react icon factory"]:::lib
210
+ LucideIconMap["LucideIconMap <br/> icon name to component"]:::lib
211
+ end
212
+
213
+ subgraph Render ["Rendering Engine"]
214
+ Player["Remotion Player"]:::frontend
215
+ CompositionRoot["CompositionRoot <br/> Composition and Root"]:::frontend
216
+ SceneComponent["SceneComponent <br/> user component instance"]:::frontend
217
+ Timeline["TimelineController <br/> play, seek, frame"]:::frontend
218
+ RemotionHooks["Remotion Hooks <br/> useCurrentFrame, useVideoConfig"]:::frontend
219
+ TailwindRuntime["Tailwind Runtime <br/> generate utility CSS"]:::frontend
220
+ StyleCache["StyleCache <br/> css text, invalidation"]:::frontend
221
+ end
222
+
223
+ subgraph BrowserAPIs ["Browser APIs"]
224
+ FileAPI["File/Blob API"]:::api
225
+ ObjectURL["URL.createObjectURL"]:::api
226
+ RAF["requestAnimationFrame"]:::api
227
+ Storage["localStorage/IndexedDB <br/> optional persistence"]:::api
228
+ Network["fetch and CDN <br/> esm.sh, jsdelivr"]:::api
229
+ end
230
+
231
+ end
232
+
233
+ CodeEditor -->|code change| ProjectStore
234
+ PromptPanel -->|generated TSX| ProjectStore
235
+ SettingsEditor -->|settings change| SettingsStore
236
+
237
+ AssetPanel -->|selected files| FileAPI
238
+ FileAPI -->|read as Blob| ObjectURL
239
+ ObjectURL -->|blob URL| AssetStore
240
+ AssetPanel -->|delete or rename| AssetStore
241
+
242
+ LibraryManager -->|add or remove| LibraryStore
243
+ LibraryManager -->|install request| LibraryLoader
244
+ IconPicker -->|search icons| LucideCatalog
245
+ IconPicker -->|insert import and use| CodeEditor
246
+
247
+ ProjectStore -->|raw TSX| SandboxFacade
248
+ SettingsStore -->|player props| CompositionRoot
249
+ AssetStore -->|asset map| ImportResolver
250
+ LibraryStore -->|enabled libraries| LibraryResolver
251
+ LucideCatalog --> LucideIconMap
252
+
253
+ SandboxFacade -->|step 1 resolve| ImportResolver
254
+
255
+ ImportResolver -->|bare import request| LibraryResolver
256
+ LibraryResolver -->|lookup| LibraryRegistry
257
+ LibraryResolver -->|resolved library module| ImportResolver
258
+
259
+ ImportResolver -->|resolved source| SyntaxChecker
260
+ SyntaxChecker -->|step 2 parse| Compiler
261
+ Compiler -->|CommonJS module| ModuleCache
262
+ ModuleCache -->|cached module| ScopeFactory
263
+
264
+ LibraryLoader -->|fetch package| Network
265
+ Network -->|module payload| LibraryLoader
266
+ LibraryLoader --> ModuleFormatAdapter
267
+ ModuleFormatAdapter --> LibraryRegistry
268
+
269
+ LibraryLoader -->|library css| LibraryStyleLoader
270
+ LibraryStyleLoader -->|inject styles| StyleCache
271
+
272
+ DependencyContainer -->|React runtime| LucideAdapter
273
+ LucideAdapter --> LucideIconMap
274
+ LucideIconMap --> LibraryRegistry
275
+ LibraryRegistry -->|library modules| DependencyContainer
276
+
277
+ DependencyContainer -->|allowed deps| ScopeFactory
278
+ ScopeFactory -->|isolated scope| RuntimeEvaluator
279
+ RuntimeEvaluator -->|React component| SceneComponent
280
+
281
+ SyntaxChecker -.->|syntax error| ErrorMapper
282
+ Compiler -.->|transform error| ErrorMapper
283
+ RuntimeEvaluator -.->|runtime error| ErrorMapper
284
+ LibraryLoader -.->|load error| ErrorMapper
285
+ ErrorMapper -->|friendly error| SandboxState
286
+ SandboxState -->|show status| StatusBar
287
+
288
+ SceneComponent -->|render tree| CompositionRoot
289
+ CompositionRoot -->|mounted composition| Player
290
+ Timeline -->|current frame| Player
291
+ Player -->|frame render| SceneComponent
292
+ RemotionHooks -->|frame and config| SceneComponent
293
+
294
+ SceneComponent -->|class names| TailwindRuntime
295
+ TailwindRuntime -->|generated CSS| StyleCache
296
+ StyleCache -->|inject styles| Player
297
+
298
+ Player -->|animation loop| RAF
299
+
300
+ ProjectStore -.->|autosave| Storage
301
+ SettingsStore -.->|autosave| Storage
302
+ AssetStore -.->|metadata save| Storage
303
+ LibraryStore -.->|installed libs save| Storage
304
+ ```
305
+
306
+ ---
307
+
308
+ ## 📦 Поддержка внешних NPM библиотек (Library Runtime)
309
+
310
+ Одной из главных фич платформы является способность "на лету" разрешать сторонние зависимости прямо в браузере, так же, как это делают CodeSandbox или StackBlitz.
311
+
312
+ ### Как это работает:
313
+ 1. **Перехват импортов (ImportResolver):**
314
+ Когда пользователь или ИИ пишет: `import { motion } from "framer-motion"`, `ImportResolver` понимает, что это *bare import* (запрос внешнего пакета, а не локального файла).
315
+ 2. **Загрузка через CDN (LibraryLoader):**
316
+ Запрос отправляется на CDN-провайдер (например, `https://esm.sh/framer-motion`), который возвращает пакет, собранный для браузера (ESM формат).
317
+ 3. **Адаптация модулей (ModuleFormatAdapter):**
318
+ Так как наш `Compiler` (Sucrase) превращает TSX в CommonJS (используя `require`), адаптер конвертирует полученный с CDN ESM-модуль так, чтобы он был доступен через `DependencyContainer`.
319
+ 4. **Кэширование (LibraryRegistry):**
320
+ Скачанный модуль сохраняется в реестре. При следующем рендере сеть не используется.
321
+ 5. **Подгрузка CSS (LibraryStyleLoader):**
322
+ Если библиотека поставляется с CSS (например, `import "swiper/css"`), лоадер скачивает стили и инжектит их в `StyleCache`, чтобы они сразу применились к видео.
323
+
324
+ ### Особенность: Lucide Icons
325
+ Для работы с иконками `lucide-react` реализован отдельный микро-пайплайн. Вместо загрузки всей тяжелой библиотеки, `LucideAdapter` динамически мапит имена иконок (из `LucideCatalog`) и создает компоненты иконок "по требованию".
326
+
327
+ ---
328
+
329
+ ## ⚙️ Детальное описание подсистем
330
+
331
+ ### 1. UI Layer & State (Пользовательский интерфейс)
332
+ Интерфейс строится на React и менеджере состояний (Zustand/Redux).
333
+ - **CodeEditor:** Текстовый редактор (Monaco) для ручного написания кода.
334
+ - **AssetPanel:** Загрузка медиа (Drag & drop). Файлы не улетают на сервер. Браузерный `URL.createObjectURL()` мгновенно превращает локальный MP4/PNG в ссылку (`blob:http://...`), которая сохраняется в `AssetStore`.
335
+ - **Auto-Save:** Все состояния (`ProjectStore`, `SettingsStore`, `AssetStore`) автоматически сохраняются в `localStorage` или `IndexedDB`.
336
+
337
+ ### 2. Sandbox Engine (Ядро компиляции)
338
+ Оркестратор, превращающий строку текста в работающий React-компонент.
339
+ - **SyntaxChecker:** Предварительная проверка AST дерева. Если есть ошибка, `ErrorMapper` переводит ее в понятный вид и показывает в редакторе (подчеркивает красным).
340
+ - **SucraseCompiler:** Самый быстрый транспилятор TSX -> JS. Вырезает типы TypeScript и превращает JSX в `React.createElement`.
341
+ - **ScopeFactory & RuntimeEvaluator:** Безопасное выполнение скомпилированного JS-кода через `new Function()`. `ScopeFactory` осуществляет "затенение" (Shadowing) глобальных объектов (`window`, `document`), предотвращая XSS и доступ к глобальному API браузера.
342
+
343
+ ### 3. Rendering Engine (Рендеринг видео)
344
+ - **Remotion Player:** Сердце видеоплеера. Принимает смонтированную композицию (`CompositionRoot`) и управляет таймлайном через `requestAnimationFrame`.
345
+ - **Tailwind Runtime:** Сканирует отрендеренный `SceneComponent` на наличие утилитарных классов (например, `className="bg-red-500 flex"`). Генерирует CSS правила "на лету" и отправляет их в `StyleCache`, который инжектит их в DOM плеера.
346
+
347
+ ---
348
+
349
+ ## 🔄 Жизненный цикл (Data Flow)
350
+
351
+ Что происходит, когда ИИ генерирует новый TSX или пользователь нажимает клавишу в редакторе:
352
+
353
+ 1. Строка кода обновляется в `ProjectStore`.
354
+ 2. `SandboxFacade` инициирует сборку.
355
+ 3. `ImportResolver` сканирует все `import ... from ...` в коде.
356
+ - Локальные ассеты заменяются на `blob:` ссылки из `AssetStore`.
357
+ - Неизвестные NPM библиотеки отправляются в `LibraryLoader` -> скачиваются с `esm.sh` -> попадают в `LibraryRegistry`.
358
+ 4. Код парсится (`SyntaxChecker`) и компилируется (`Compiler`).
359
+ 5. `DependencyContainer` собирает `require`-объекты (React, Remotion, скачанные NPM-либы).
360
+ 6. `RuntimeEvaluator` выполняет изолированный JS-код, возвращая функцию React-компонента.
361
+ 7. Компонент монтируется в `SceneComponent`.
362
+ 8. `Tailwind Runtime` перехватывает классы и генерирует CSS.
363
+ 9. `Remotion Player` отображает кадр.
364
+
365
+ ---
366
+
367
+ ## 🛠 Установка и использование (Псевдокод внедрения)
368
+
369
+ ```bash
370
+ npm install browser-tsx-sandbox remotion @twind/core
371
+ ```
372
+
373
+ **Пример использования SandboxFacade:**
374
+
375
+ ```tsx
376
+ import { SandboxFacade } from 'browser-tsx-sandbox';
377
+ import { useProjectStore, useAssetStore, useLibraryStore } from './store';
378
+
379
+ function PreviewPipeline() {
380
+ const code = useProjectStore(state => state.code);
381
+ const blobAssets = useAssetStore(state => state.map); // { "logo.png": "blob:..." }
382
+
383
+ const { Component, error } = useAsyncMemo(async () => {
384
+ const facade = new SandboxFacade();
385
+
386
+ // 1. Конфигурируем зависимости и ресурсы
387
+ facade.setAssets(blobAssets);
388
+
389
+ // 2. Песочница сама скачает недостающие библиотеки из импортов (NPM)
390
+ return await facade.compileAndEvaluate(code);
391
+ }, [code, blobAssets]);
392
+
393
+ if (error) return <ErrorOverlay error={error} />;
394
+
395
+ return <RemotionPlayer component={Component} {...playerSettings} />;
396
+ }
397
+ ```
398
+ ---
399
+
400
+ ## 📦 Сборка и публикация пакета
401
+
402
+ ```bash
403
+ npm run build # tsup -> dist/ (ESM + CJS + .d.ts)
404
+ npm run pack:check # npm pack --dry-run: содержимое тарбола
405
+ npm pack # собрать browser-tsx-sandbox-<version>.tgz
406
+ ```
407
+
408
+ - Форматы: `dist/index.js` (ESM), `dist/index.cjs` (CJS), `dist/index.d.ts` (`exports`-карта настроена).
409
+ - В публикацию попадают только `dist/`, `README.md`, `API.md` (поле `files`).
410
+ - `react` — `peerDependency`, `sucrase` и `fflate` — `dependencies`; все они `external` и не бандлятся.
411
+ - `prepack` автоматически собирает `dist/`, `prepublishOnly` прогоняет `typecheck` + `test`.
412
+ - Публикация: `npm publish` (при необходимости `--access public`).
413
+
414
+ Сгенерированные артефакты (`dist/`, `render/out`, `render/bundle*`, `render/.generated`, `render/public`, `*.tgz`) не коммитятся — см. `.gitignore`.
415
+
416
+ ## ✅ Проверка выдачи видео
417
+
418
+ `render/verify-video.mjs` — автономный парсер MP4 (ISO BMFF): проверяет `ftyp`/`moov`, кодек `avc1`, размеры кадра и длительность. Если в системе есть `ffprobe`, дополнительно сверяет кодек, число кадров и длительность.
419
+
420
+ ```bash
421
+ npm run verify:video render/out/sandbox.mp4 # можно и директорию с .mp4
422
+ ```
423
+
424
+ Пример: `majorBrand: isom`, `h264`, `640x360`, `60` кадров, `2.0s`, `valid: true`. Тест — `render/video.e2e.test.ts`.
425
+
426
+ ---
427
+
428
+ *Создано для систем AI-видеогенерации нового поколения. 100% Client-Side. 100% Freedom.*