Open general

pup render path: let Generate use the host browser when the container cannot run Chrome

Colby Knox · 2mo ago

Follow-up to #6. As of 0.10.1, hero-studio doctor names what a light container is missing and pose refuses to open the gate until the renderer works. That solves the diagnosis; the remediation still requires installing a browser runtime (puppeteer-core, Chrome, and its OS libraries) into a container that was deliberately built without one.

There is a structural alternative for containers where that is off the table: render through pup. Generate is fundamentally "open the studio's ?render=1 view and screenshot #hero", and pup drives Chrome on the host machine, which does not care what is in the container. Drew's report in #6 noted exactly this asymmetry: pup worked throughout while the in-container renderer had nothing to run on.

Sketch: when the server-side renderer is unavailable and Adom Desktop answers, Generate falls back to a pup render: browser_open_window on a hidden or dedicated render session pointed at the render view, size the viewport for a 2000x1250 capture, browser_screenshot, hand the bytes back to the server for the downscale and provenance stamp (python3+PIL stays a container-side requirement, which is a far smaller ask than Chrome).

To verify before building:

  • exact element capture at 2000x1250 through the host browser (viewport control, device pixel ratio, font rendering parity with the headless render)
  • whether browser_screenshot can return an element clip or only the viewport
  • how the PNG bytes travel back into the container (file path vs base64 over the adom-desktop CLI)
  • provenance semantics: the stamp should record renderer: pup so a host-rendered hero is distinguishable

Not scheduled; this is the "no browser in the container, ever" escape hatch, and doctor + remediation covers the known cases today.

3 Replies

Colby Knox · 2mo ago

Implemented and shipped in 0.11.0, ahead of schedule: Colby wants this solved, not parked.

What shipped:

  • render-pup.mjs: opens the studio's ?render=1 view through the workspace proxy URL in a dedicated hero-render pup session (never the human's studio window), waits for #hero, resizes the window toward the gate's 1600px floor, measures the hero rect and devicePixelRatio (numbers-only sentinel transport, immune to whatever JSON wrapping browser_eval applies), screenshots (a returned path and inline base64 are both handled), crops container-side with PIL, and warns when the host viewport clipped the hero (aspect drift) or undershot 1600px.
  • Renderer ladder in the server: local headless Chrome first (full fidelity), pup second. Both failures combine in the error. The provenance stamp now records renderer: local or renderer: pup, so a host-rendered hero is distinguishable, as sketched above.
  • hero-studio doctor now checks the pup leg (desktop relay answers + proxy URL present) and reports overall readiness as PIL and (local or pup). A light container with a working desktop relay is READY out of the box; pose opens the gate and says which leg will render.
  • Pin hooks for testing and control: POST /generate {"renderer":"pup"} per request, HERO_RENDERER env server-wide.

Verified here: syntax, doctor's new decision logic, the auto ladder (local wins, stamp says renderer: local), and the pup leg failing fast with a precise message on a container with no desktop relay. What this container CANNOT verify is the pup happy path: no relay reaches it.

@Drew Owens, you have the one environment that can close this loop, and your #6 report is why both halves of this feature exist. On your HD container after adom-wiki pkg install adom/hero-studio (0.11.0):

  1. hero-studio doctor - expect the pup fallback check green.
  2. With the studio open: curl -s -X POST localhost:8846/generate -H 'Content-Type: application/json' -d '{"renderer":"pup"}' - a render window should appear on the host, and the export should land in generated/ with renderer: pup in the PNG comment chunk.
  3. The things I had to assume about browser_eval/browser_screenshot output formats are handled defensively, but if a step dies, the stderr line it prints names exactly which assumption broke - paste it here and I will fix same-day.

Leaving this open until that verification lands.

Colby Knox · 2mo ago

Heads-up for the verification run: 0.11.1 hardened this path after Caleb hit it in the wild (#8, closed). The pup leg no longer trusts the relay: it paints a magenta sentinel in the render window's viewport corner, requires it in the screenshot pixels (missing = wrong window captured, hard fail with that diagnosis), uses its found position as the viewport origin so native shots with window chrome crop correctly, and hard-fails any crop outside 16:10. The server additionally refuses to stamp a wrong-aspect PNG from either leg.

So the updated expectation for step 2 on HD: either a clean renderer: pup hero in generated/, or a one-line stderr diagnosis naming exactly which assumption broke (NO-SENTINEL wrong-window, BAD-ASPECT clipped viewport, or an eval/screenshot transport mismatch). Both outcomes are useful; garbage output is no longer a possible outcome.

Colby Knox · 28d ago

Data point from Ray's #58 (2026-09-22, Hydrogen container, Node 18): with the local renderer missing, the pup leg failed after two minutes with 'render view never became ready (#hero not found at http://8846.localhost:47083/?render=1)'. That is the host browser failing to load the proxied render view at all, not the #57 slot-size loop (fixed in 0.21.2) and not the missing local dependency (fixed in 0.21.3, which now also refuses up front when neither leg can render). Whatever VSCODE_PROXY_URI expands to on that box is not a hostname the host browser resolves. Left open here as the pup reachability question.

Log in to reply.