@1agh/maude 0.41.0 → 0.43.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/annotations-layer.tsx +1055 -278
- package/apps/studio/annotations-model.ts +58 -0
- package/apps/studio/api.ts +173 -63
- package/apps/studio/bin/_html-playwright.mjs +26 -4
- package/apps/studio/bin/_ingest-footage.mjs +386 -0
- package/apps/studio/bin/_pdf-playwright.mjs +13 -2
- package/apps/studio/bin/_png-playwright.mjs +15 -2
- package/apps/studio/bin/_pptx-playwright.mjs +17 -4
- package/apps/studio/bin/_probe-footage-playwright.mjs +269 -0
- package/apps/studio/bin/_screenshot-playwright.mjs +17 -2
- package/apps/studio/bin/_svg-playwright.mjs +26 -4
- package/apps/studio/bin/_video-playwright.mjs +141 -71
- 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 +160 -0
- package/apps/studio/bin/probe-footage.sh +28 -0
- package/apps/studio/bin/read-annotations.mjs +125 -2
- package/apps/studio/bin/screenshot.sh +53 -4
- package/apps/studio/canvas-edit.ts +459 -10
- package/apps/studio/canvas-lib.tsx +523 -5
- package/apps/studio/canvas-pipeline.ts +65 -7
- package/apps/studio/canvas-shell.tsx +156 -16
- package/apps/studio/client/app.jsx +476 -88
- package/apps/studio/client/export-center.jsx +460 -0
- package/apps/studio/client/github.js +4 -0
- package/apps/studio/client/panels/GitPanel.jsx +8 -0
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +133 -21
- 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 +437 -0
- package/apps/studio/client/styles/3-shell-maude.css +59 -0
- package/apps/studio/client/styles/4-components.css +150 -0
- package/apps/studio/commands/edit-source-command.ts +31 -4
- package/apps/studio/config.schema.json +2 -2
- package/apps/studio/context-menu.tsx +31 -6
- package/apps/studio/dist/client.bundle.js +5561 -19
- 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/export-dialog.tsx +18 -25
- package/apps/studio/exporters/_runtime.ts +104 -0
- package/apps/studio/exporters/html.ts +12 -20
- package/apps/studio/exporters/index.ts +14 -2
- package/apps/studio/exporters/jobs.ts +376 -0
- package/apps/studio/exporters/pdf.ts +16 -20
- package/apps/studio/exporters/png.ts +12 -20
- package/apps/studio/exporters/pptx.ts +22 -23
- package/apps/studio/exporters/scope.ts +1 -0
- package/apps/studio/exporters/svg.ts +14 -22
- package/apps/studio/exporters/video.ts +37 -19
- package/apps/studio/footage/schema.test.ts +195 -0
- package/apps/studio/footage/schema.ts +484 -0
- package/apps/studio/footage-store.ts +235 -0
- package/apps/studio/fs-watch.ts +10 -0
- package/apps/studio/git/endpoints.ts +68 -7
- package/apps/studio/git/service.ts +198 -20
- package/apps/studio/github/service.ts +67 -0
- package/apps/studio/handoff.ts +3 -2
- package/apps/studio/http.ts +318 -64
- package/apps/studio/input-router.tsx +10 -0
- package/apps/studio/inspect.ts +53 -1
- package/apps/studio/paths.ts +10 -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/runtime-bundle.ts +29 -6
- package/apps/studio/server.ts +8 -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/acp-bootstrap-brief.test.ts +11 -0
- package/apps/studio/test/acp-commands.test.ts +2 -1
- 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 +19 -0
- package/apps/studio/test/dns-rebinding-guard.test.ts +27 -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 +46 -0
- package/apps/studio/test/export-center.test.tsx +287 -0
- package/apps/studio/test/export-shim-multi-capture.test.ts +83 -0
- package/apps/studio/test/exporters/history.test.ts +32 -3
- package/apps/studio/test/exporters/jobs.test.ts +263 -0
- package/apps/studio/test/footage-store.test.ts +100 -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/input-router.test.ts +33 -0
- package/apps/studio/test/inspect-script-syntax.test.ts +53 -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/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/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 +43 -20
- 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 +38 -1
- package/apps/studio/use-selection-set.tsx +21 -0
- package/apps/studio/video-comp.tsx +48 -7
- package/apps/studio/whats-new.json +70 -0
- package/apps/studio/ws.ts +6 -0
- package/cli/commands/design.mjs +34 -3
- package/cli/lib/gitignore-block.mjs +2 -0
- package/package.json +10 -10
- package/plugins/design/templates/_shell.html +1 -0
|
@@ -463,6 +463,31 @@ export function hoverTargetToSelection(target: HoverTarget, file?: string): Sele
|
|
|
463
463
|
worldW: el instanceof HTMLElement ? Math.round(el.offsetWidth) : undefined,
|
|
464
464
|
worldH: el instanceof HTMLElement ? Math.round(el.offsetHeight) : undefined,
|
|
465
465
|
html: el ? (el.outerHTML ?? '').slice(0, 4000) : '',
|
|
466
|
+
// feature-photo-editor (Task 14) — flag a content-addressed artboard `<img>`
|
|
467
|
+
// so the Inspector can offer the Photo tab. Only a real `assets/<sha8>.<ext>`
|
|
468
|
+
// src qualifies (an external URL / SVG icon / data: URI has no sidecar).
|
|
469
|
+
// `data-photo-asset` (stamped by canvas-lib's PhotoPreviewBridge the first
|
|
470
|
+
// time it bakes an edit into this element) is checked FIRST — once an edit
|
|
471
|
+
// is applied, the live `src` is a `data:` URL (the baked composite), which
|
|
472
|
+
// no longer matches the asset regex. Without this, an already-edited photo
|
|
473
|
+
// would lose its Photo tab the moment you select it (the exact regression
|
|
474
|
+
// this fixes — the bridge's own direct-src-swap made the element's `src`
|
|
475
|
+
// stop being a reliable asset key).
|
|
476
|
+
...(() => {
|
|
477
|
+
if (el?.tagName?.toLowerCase() !== 'img') return {};
|
|
478
|
+
// The canvas iframe is untrusted content (DDR-054) — an authored `<img
|
|
479
|
+
// data-photo-asset="...">` is attacker-controllable, so the tag is only
|
|
480
|
+
// trusted when it actually has the `assets/<sha8>.<ext>` shape (security
|
|
481
|
+
// review finding: an unshaped value would ride unbounded into
|
|
482
|
+
// `_active.json`/the WS broadcast via inspect.ts's `enrich()`).
|
|
483
|
+
const assetRe = /assets\/[0-9a-f]{8}\.[a-z0-9]+/i;
|
|
484
|
+
const tagged = (el as HTMLElement).getAttribute?.('data-photo-asset');
|
|
485
|
+
if (tagged && assetRe.test(tagged))
|
|
486
|
+
return { photoKind: 'artboard-img' as const, photoAsset: tagged };
|
|
487
|
+
const src = (el as HTMLImageElement).getAttribute?.('src') || '';
|
|
488
|
+
const m = assetRe.exec(src);
|
|
489
|
+
return m ? { photoKind: 'artboard-img' as const, photoAsset: m[0] } : {};
|
|
490
|
+
})(),
|
|
466
491
|
...styleMapsFor(el),
|
|
467
492
|
};
|
|
468
493
|
}
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
* @scope apps/studio/export-dialog.tsx
|
|
4
4
|
* @purpose Native `<dialog>`-based export modal. Three controls — format,
|
|
5
5
|
* scope, per-format options — plus a Recent tab populated by
|
|
6
|
-
* `/_api/export-history`. Submit
|
|
7
|
-
*
|
|
6
|
+
* `/_api/export-history`. Submit POSTs `/_api/export-jobs` and
|
|
7
|
+
* closes immediately (feature-background-export-notification-
|
|
8
|
+
* center) — the main-shell notification center owns status/
|
|
9
|
+
* progress/completion from there, not this dialog. `⌘E` opens
|
|
8
10
|
* the dialog from anywhere inside the canvas; `⌘⇧E` re-runs the
|
|
9
11
|
* most recent export without opening (T10 fast path).
|
|
10
12
|
*
|
|
@@ -293,7 +295,9 @@ export function ExportDialogProvider({ children }: { children: ReactNode }): Rea
|
|
|
293
295
|
dialogRef.current?.close();
|
|
294
296
|
}, []);
|
|
295
297
|
|
|
296
|
-
//
|
|
298
|
+
// Loaded when the dialog opens. Submit now closes the dialog immediately
|
|
299
|
+
// (the job runs in the background) instead of refreshing this in place —
|
|
300
|
+
// the next open picks up whatever's finished by then.
|
|
297
301
|
const loadHistory = useCallback(async () => {
|
|
298
302
|
try {
|
|
299
303
|
if (isCrossOriginFramed()) {
|
|
@@ -364,10 +368,13 @@ export function ExportDialogProvider({ children }: { children: ReactNode }): Rea
|
|
|
364
368
|
setStatus(null);
|
|
365
369
|
try {
|
|
366
370
|
if (isCrossOriginFramed()) {
|
|
367
|
-
// Bridge through the main shell — the iframe can't reach
|
|
368
|
-
// (canvas origin 403s it). The parent
|
|
369
|
-
//
|
|
370
|
-
|
|
371
|
+
// Bridge through the main shell — the iframe can't reach
|
|
372
|
+
// /_api/export-jobs (canvas origin 403s it, DDR-060). The parent
|
|
373
|
+
// enqueues the job and replies with the id immediately; the
|
|
374
|
+
// trusted main-shell notification center (which already owns the
|
|
375
|
+
// WS connection) is the single place status/progress/completion
|
|
376
|
+
// live from here — this dialog no longer polls or receives bytes.
|
|
377
|
+
const res = await bridgeRequest<{ ok?: boolean; jobId?: string; error?: string }>(
|
|
371
378
|
'export-request',
|
|
372
379
|
'export-result',
|
|
373
380
|
{ payload: { format, scope, options } }
|
|
@@ -376,11 +383,10 @@ export function ExportDialogProvider({ children }: { children: ReactNode }): Rea
|
|
|
376
383
|
setStatus({ text: `Export failed: ${res.error || 'unknown'}`, isError: true });
|
|
377
384
|
return;
|
|
378
385
|
}
|
|
379
|
-
|
|
380
|
-
void loadHistory();
|
|
386
|
+
close();
|
|
381
387
|
return;
|
|
382
388
|
}
|
|
383
|
-
const r = await fetch('/_api/export', {
|
|
389
|
+
const r = await fetch('/_api/export-jobs', {
|
|
384
390
|
method: 'POST',
|
|
385
391
|
headers: { 'content-type': 'application/json' },
|
|
386
392
|
body: JSON.stringify({ format, scope, options }),
|
|
@@ -390,20 +396,7 @@ export function ExportDialogProvider({ children }: { children: ReactNode }): Rea
|
|
|
390
396
|
setStatus({ text: `Export failed: ${text || r.status}`, isError: true });
|
|
391
397
|
return;
|
|
392
398
|
}
|
|
393
|
-
|
|
394
|
-
const filename =
|
|
395
|
-
/filename="([^"]+)"/.exec(disp)?.[1] ?? `export${FORMAT_META[format].defaultExt}`;
|
|
396
|
-
const blob = await r.blob();
|
|
397
|
-
const url = URL.createObjectURL(blob);
|
|
398
|
-
const a = document.createElement('a');
|
|
399
|
-
a.href = url;
|
|
400
|
-
a.download = filename;
|
|
401
|
-
document.body.appendChild(a);
|
|
402
|
-
a.click();
|
|
403
|
-
a.remove();
|
|
404
|
-
URL.revokeObjectURL(url);
|
|
405
|
-
setStatus({ text: `Saved ${filename}`, isError: false });
|
|
406
|
-
void loadHistory();
|
|
399
|
+
close();
|
|
407
400
|
} catch (err) {
|
|
408
401
|
const msg = err instanceof Error ? err.message : String(err);
|
|
409
402
|
setStatus({ text: `Export failed: ${msg}`, isError: true });
|
|
@@ -411,7 +404,7 @@ export function ExportDialogProvider({ children }: { children: ReactNode }): Rea
|
|
|
411
404
|
setSubmitting(false);
|
|
412
405
|
}
|
|
413
406
|
},
|
|
414
|
-
[
|
|
407
|
+
[close]
|
|
415
408
|
);
|
|
416
409
|
|
|
417
410
|
const rerunLast = useCallback(async () => {
|
|
@@ -67,3 +67,107 @@ export function resolveExportRuntime(): string {
|
|
|
67
67
|
}
|
|
68
68
|
return runtime;
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
export interface SpawnShimOptions {
|
|
72
|
+
/** cwd for the spawned shim — callers pass `path.dirname(<SHIM>)`. */
|
|
73
|
+
cwd: string;
|
|
74
|
+
/** Aborts the render — kills the child process. */
|
|
75
|
+
signal?: AbortSignal;
|
|
76
|
+
/** Fired for each `MAUDE_PROGRESS {"current":N,"total":M}` stdout line. */
|
|
77
|
+
onProgress?: (update: { current: number; total: number }) => void;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface SpawnShimResult {
|
|
81
|
+
code: number;
|
|
82
|
+
/** stdout lines with `MAUDE_PROGRESS` lines filtered out (written-file paths etc.). */
|
|
83
|
+
stdoutLines: string[];
|
|
84
|
+
stderr: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const PROGRESS_LINE = /^MAUDE_PROGRESS (.+)$/;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Spawn a render shim (`bin/_{png,pdf,svg,html,pptx,video}-playwright.mjs`)
|
|
91
|
+
* via a resolved node/bun runtime, reading stdout INCREMENTALLY (not
|
|
92
|
+
* buffered via `new Response(proc.stdout).text()`) so a `MAUDE_PROGRESS`
|
|
93
|
+
* line written mid-render reaches `onProgress` as soon as it's flushed,
|
|
94
|
+
* instead of only after the whole process exits. Non-progress lines
|
|
95
|
+
* (written-file paths, the existing per-adapter contract) collect into
|
|
96
|
+
* `stdoutLines` in the order they arrived. Exit-code handling stays with the
|
|
97
|
+
* caller — this returns the same shape the old `Promise.all([...text()])`
|
|
98
|
+
* block did, so adapters keep their existing `if (code !== 0) throw …`.
|
|
99
|
+
*/
|
|
100
|
+
export async function spawnShim(args: string[], opts: SpawnShimOptions): Promise<SpawnShimResult> {
|
|
101
|
+
const proc = Bun.spawn([resolveExportRuntime(), ...args], {
|
|
102
|
+
cwd: opts.cwd,
|
|
103
|
+
stdout: 'pipe',
|
|
104
|
+
stderr: 'pipe',
|
|
105
|
+
signal: opts.signal,
|
|
106
|
+
});
|
|
107
|
+
// Bun.spawn's `signal` option kills the child on abort; this listener is a
|
|
108
|
+
// defensive fallback in case a given Bun build ignores it — cheap and
|
|
109
|
+
// idempotent (killing an already-exited process is a no-op).
|
|
110
|
+
const onAbort = () => {
|
|
111
|
+
try {
|
|
112
|
+
proc.kill();
|
|
113
|
+
} catch {
|
|
114
|
+
/* already exited */
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
opts.signal?.addEventListener('abort', onAbort);
|
|
118
|
+
|
|
119
|
+
const stderrPromise = new Response(proc.stderr).text();
|
|
120
|
+
const stdoutLines: string[] = [];
|
|
121
|
+
let buffer = '';
|
|
122
|
+
const consumeLine = (line: string) => {
|
|
123
|
+
if (!line) return;
|
|
124
|
+
const match = PROGRESS_LINE.exec(line);
|
|
125
|
+
if (!match) {
|
|
126
|
+
stdoutLines.push(line);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const update = JSON.parse(match[1]) as { current: number; total: number };
|
|
131
|
+
opts.onProgress?.(update);
|
|
132
|
+
} catch {
|
|
133
|
+
/* malformed progress line — drop it rather than treat it as a file path */
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const reader = proc.stdout.pipeThrough(new TextDecoderStream()).getReader();
|
|
139
|
+
for (;;) {
|
|
140
|
+
const { done, value } = await reader.read();
|
|
141
|
+
if (done) break;
|
|
142
|
+
buffer += value;
|
|
143
|
+
let newlineIdx = buffer.indexOf('\n');
|
|
144
|
+
while (newlineIdx !== -1) {
|
|
145
|
+
consumeLine(buffer.slice(0, newlineIdx).trim());
|
|
146
|
+
buffer = buffer.slice(newlineIdx + 1);
|
|
147
|
+
newlineIdx = buffer.indexOf('\n');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (buffer.trim()) consumeLine(buffer.trim());
|
|
151
|
+
} finally {
|
|
152
|
+
opts.signal?.removeEventListener('abort', onAbort);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const [stderr, code] = await Promise.all([stderrPromise, proc.exited]);
|
|
156
|
+
return { code, stdoutLines, stderr };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* `spawnShim()` + the exit-code check every adapter did identically
|
|
161
|
+
* (`if (code !== 0) throw new Error(...)`; `code review, /flow:done` — six
|
|
162
|
+
* near-copies collapsed into one). `args[0]` is the shim path by convention
|
|
163
|
+
* (every adapter builds it that way), used only for the error message.
|
|
164
|
+
*/
|
|
165
|
+
export async function runShim(args: string[], opts: SpawnShimOptions): Promise<string[]> {
|
|
166
|
+
const { code, stdoutLines, stderr } = await spawnShim(args, opts);
|
|
167
|
+
if (code !== 0) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`${path.basename(args[0])} exited ${code}: ${stderr.trim() || stdoutLines.join('\n').trim()}`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return stdoutLines;
|
|
173
|
+
}
|
|
@@ -11,10 +11,11 @@ import { tmpdir } from 'node:os';
|
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
|
|
13
13
|
import JSZip from 'jszip';
|
|
14
|
-
import { exportShimPath,
|
|
14
|
+
import { exportShimPath, runShim } from './_runtime.ts';
|
|
15
15
|
import {
|
|
16
16
|
canvasShellUrl,
|
|
17
17
|
type ExportContext,
|
|
18
|
+
type ExportHooks,
|
|
18
19
|
type ExportOptions,
|
|
19
20
|
type ExportResult,
|
|
20
21
|
} from './index.ts';
|
|
@@ -27,7 +28,8 @@ async function captureHtml(
|
|
|
27
28
|
target: Extract<Target, { kind: 'element' }>,
|
|
28
29
|
ctx: ExportContext,
|
|
29
30
|
outDir: string,
|
|
30
|
-
timeoutSec: number
|
|
31
|
+
timeoutSec: number,
|
|
32
|
+
hooks?: ExportHooks
|
|
31
33
|
): Promise<string[]> {
|
|
32
34
|
const args = [
|
|
33
35
|
HTML_PLAYWRIGHT,
|
|
@@ -46,29 +48,18 @@ async function captureHtml(
|
|
|
46
48
|
if (target.widen) args.push('--widen-to-artboard', '1');
|
|
47
49
|
args.push('--out', path.join(outDir, `${target.canvasSlug}.html`));
|
|
48
50
|
}
|
|
49
|
-
|
|
51
|
+
return runShim(args, {
|
|
50
52
|
cwd: path.dirname(HTML_PLAYWRIGHT),
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
signal: hooks?.signal,
|
|
54
|
+
onProgress: hooks?.onProgress,
|
|
53
55
|
});
|
|
54
|
-
const [stdout, stderr] = await Promise.all([
|
|
55
|
-
new Response(proc.stdout).text(),
|
|
56
|
-
new Response(proc.stderr).text(),
|
|
57
|
-
]);
|
|
58
|
-
const code = await proc.exited;
|
|
59
|
-
if (code !== 0) {
|
|
60
|
-
throw new Error(`_html-playwright exited ${code}: ${stderr.trim() || stdout.trim()}`);
|
|
61
|
-
}
|
|
62
|
-
return stdout
|
|
63
|
-
.split('\n')
|
|
64
|
-
.map((s) => s.trim())
|
|
65
|
-
.filter(Boolean);
|
|
66
56
|
}
|
|
67
57
|
|
|
68
58
|
export async function run(
|
|
69
59
|
targets: Target[],
|
|
70
60
|
options: ExportOptions,
|
|
71
|
-
ctx: ExportContext
|
|
61
|
+
ctx: ExportContext,
|
|
62
|
+
hooks?: ExportHooks
|
|
72
63
|
): Promise<ExportResult> {
|
|
73
64
|
if (!targets.length) {
|
|
74
65
|
return { filename: 'export.zip', contentType: 'application/zip', body: new Uint8Array(0) };
|
|
@@ -83,9 +74,10 @@ export async function run(
|
|
|
83
74
|
const tmp = mkdtempSync(path.join(tmpdir(), 'maude-html-'));
|
|
84
75
|
try {
|
|
85
76
|
const written: string[] = [];
|
|
86
|
-
for (
|
|
87
|
-
const paths = await captureHtml(
|
|
77
|
+
for (let i = 0; i < elementTargets.length; i += 1) {
|
|
78
|
+
const paths = await captureHtml(elementTargets[i], ctx, tmp, timeoutSec, hooks);
|
|
88
79
|
written.push(...paths);
|
|
80
|
+
hooks?.onProgress?.({ current: i + 1, total: elementTargets.length });
|
|
89
81
|
}
|
|
90
82
|
if (!written.length) {
|
|
91
83
|
return { filename: 'export.zip', contentType: 'application/zip', body: new Uint8Array(0) };
|
|
@@ -74,8 +74,19 @@ export interface ExportResult {
|
|
|
74
74
|
body: Uint8Array;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/** Progress + cancellation hooks threaded through to the shim-spawning layer. */
|
|
78
|
+
export interface ExportHooks {
|
|
79
|
+
onProgress?: (update: { current: number; total: number }) => void;
|
|
80
|
+
signal?: AbortSignal;
|
|
81
|
+
}
|
|
82
|
+
|
|
77
83
|
export interface Adapter {
|
|
78
|
-
run(
|
|
84
|
+
run(
|
|
85
|
+
targets: Target[],
|
|
86
|
+
options: ExportOptions,
|
|
87
|
+
ctx: ExportContext,
|
|
88
|
+
hooks?: ExportHooks
|
|
89
|
+
): Promise<ExportResult>;
|
|
79
90
|
}
|
|
80
91
|
|
|
81
92
|
const REGISTRY: Record<Format, Adapter> = {
|
|
@@ -114,6 +125,7 @@ export async function runExport(args: {
|
|
|
114
125
|
options: ExportOptions;
|
|
115
126
|
resolve: Omit<ResolveScopeArgs, 'scope'>;
|
|
116
127
|
ctx: ExportContext;
|
|
128
|
+
hooks?: ExportHooks;
|
|
117
129
|
}): Promise<ExportResult> {
|
|
118
130
|
// Thread the options bag into the resolver so submit-time selection /
|
|
119
131
|
// active-artboard hints (DDR — selection-passthrough) win over the persisted
|
|
@@ -123,7 +135,7 @@ export async function runExport(args: {
|
|
|
123
135
|
if (!adapter) {
|
|
124
136
|
throw new Error(`unknown format: ${args.format}`);
|
|
125
137
|
}
|
|
126
|
-
return adapter.run(targets, args.options, args.ctx);
|
|
138
|
+
return adapter.run(targets, args.options, args.ctx, args.hooks);
|
|
127
139
|
}
|
|
128
140
|
|
|
129
141
|
export type { Scope, Target };
|