Skip to content

Add headless CLI render surface for agent authoring - #20

Merged
lenxism merged 8 commits into
mainfrom
feat/headless-render
Jul 22, 2026
Merged

Add headless CLI render surface for agent authoring#20
lenxism merged 8 commits into
mainfrom
feat/headless-render

Conversation

@lenxism

@lenxism lenxism commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add a local-first mockstudio CLI (compose / validate / render / presets / kit) so agents can author Scene Documents and export framed still/video ingredients without the editor UI.
  • Drive the existing Exporter through Playwright + a minimal localhost capture page (Source Image and Source Video), with --json feedback, sparse progress, exit codes, optional alpha WebM/MOV, and Stage-sized defaults.
  • Split CI into non-GPU encode/export smoke and GPU soft still goldens (MAE) to catch Look/Device/Artboard drift.

Closes the implementation track under .scratch/headless-render/ (issues 01–07).

Test plan

  • pnpm typecheck && pnpm lint && pnpm test && pnpm build
  • pnpm exec mockstudio --help and per-command help for compose/validate/render/presets/kit
  • Compose from a Look Preset → validate → render a PNG still with --media and --json
  • Render an H.264 MP4 from Source Video; confirm Stage size, --fps, --at, --preview, and stderr progress
  • Attempt alpha WebM/MOV on transparent vs opaque Stage; confirm the transparency gate
  • Confirm Source Media pixels are never written into Scene Document / kit examples
  • CI: smoke job green without GPU; soft goldens job runs on GPU runners when available

Note

Medium Risk
Large new render/encode surface and Playwright boot path, but output still goes through the shared Exporter with broad contract tests; GPU goldens are opt-in only.

Overview
Introduces a mockstudio binary with compose, validate, render, presets, and kit so Scene Documents can be built and exported without the editor. render drives the same Exporter as the UI via a localhost ?render page, Playwright, and window.__mockstudio (document load, media bind, ready gate, capture bytes).

Core gains compose/validate/learning-kit/merge-patch helpers plus CLI-oriented export rules: Stage short-edge sizing, WebP stills, WebM/MOV alpha video gated on a transparent Stage, and structured --json / exit codes for agents.

CI splits non-GPU verify (Playwright + ffmpeg encode smoke) from an opt-in GPU job for soft still goldens (MAE vs committed PNGs); goldens are skipped on default runners.

Reviewed by Cursor Bugbot for commit 995676b. Bugbot is set up for automated code reviews on this repo. Configure here.

lenxism added 8 commits July 21, 2026 13:46
Agents can inspect curated Look/Camera/Motion Presets and dump an offline kit (schema, catalog, annotated examples) without opening the editor or launching Chromium.
Materialize Scene Documents from curated Looks / files / default with
optional camera, motion, and RFC 7396 merge-patch; validate returns
stable schema and semantic codes under --json without silent recovery.
Pass PNG/JPEG dimensions into core semantic checks so texture_too_large
surfaces under --json, and share JSON file reads across compose/validate.
Mounts the existing Exporter path without editor chrome and exposes a thin
bridge so the CLI driver can load a Scene Document, bind Source Media, and
capture on a secure 127.0.0.1 origin.
Wire `mockstudio render` through Playwright + the minimal capture page so agents get Stage-sized PNG/JPG/WebP ingredients with the CLI feedback envelope.
…ity.

One render grammar now exports H.264 MP4 at Stage size, accepts Source Video
with --at/--preview, and emits sparse progress plus the full exit-code contract.
Agents can request VP9+alpha WebM (and MOV ProRes when Chromium can encode it) via the shared Exporter, with a clear Stage-transparency gate so opaque Mockups never yield silent opaque files under alpha extensions.
Catch Look/Device/Artboard regressions with MAE soft-compare on GPU runners while non-GPU CI keeps encode/export smoke only.
@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mockstudio Ready Ready Preview, Comment Jul 22, 2026 3:18am

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 995676b. Configure here.

Comment thread src/core/export.ts
height: size.height,
format,
transparent: false,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transparent stage stills stay opaque

Medium Severity

buildStillExportRequest always sets transparent: false, so headless still exports never ask the shared Exporter for alpha even when the Scene Document uses a transparent Stage and the format supports it (PNG/WebP). Output keeps an opaque backdrop instead of matching editor transparent still behavior.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 995676b. Configure here.

Comment thread src/cli/render.ts
document: loaded.document,
probedWidth: probed.media.width,
probedHeight: probed.media.height,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still render ignores interrupt

Medium Severity

Video render forwards AbortSignal through capture and maps SIGINT to exit 130, but the still path never passes the signal into runStillRender or captureStillInBrowser / withHeadlessPage. A long Playwright still can keep running after interrupt instead of exiting with the documented interrupted code.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 995676b. Configure here.

unsubscribe()
resolve()
})
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ready gate may hang forever

Medium Severity

waitForDisplayedSourceMedia waits until displayedSourceAspect becomes non-null whenever Source Media is bound, with no timeout or failure path. If the texture never publishes an aspect (slow decode, GPU upload failure, or a load error without updating aspect), whenReady and CLI capture never resolve.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 995676b. Configure here.

@lenxism
lenxism merged commit f997ba1 into main Jul 22, 2026
5 checks passed
lenxism added a commit that referenced this pull request Jul 22, 2026
… render (#21)

## Summary
- Transparent Stage + PNG/WebP stills now ask the Exporter for alpha
(same gate as the editor; JPEG stays opaque).
- Still capture forwards `AbortSignal` and maps abort to exit 130,
matching video encode.
- Headless ready gate times out after 30s if bound Source Media never
publishes a display aspect (was an unbounded hang).

These were local follow-ups left uncommitted when #20 merged.

## Test plan
- [x] Unit coverage for still `transparent`, still abort → 130, and
ready-gate timeout
- [ ] `pnpm test` locally (or CI) on this branch
- [ ] Optional smoke: `mockstudio render` a transparent-Stage PNG and
Ctrl+C a still render

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes export alpha semantics and CLI interrupt/headless readiness
behavior on the render path; risk is mitigated by unit tests and parity
with existing video abort handling.
> 
> **Overview**
> Headless still rendering now matches the editor for **transparency**:
`buildStillExportRequest` sets `transparent` when the Stage is
transparent and the format is PNG or WebP (JPEG stays opaque).
> 
> **Still capture** accepts an `AbortSignal`, passes it through
Playwright’s `withHeadlessPage`, and maps abort to **exit 130** with the
same interrupted JSON envelope as video encode.
> 
> The headless **ready gate** no longer waits forever when bound Source
Media never publishes a display aspect—it **rejects after 30s**
(configurable in tests via `displayedMediaTimeoutMs`).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
e2140bc. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant