Hero Studio
Public Made by Adomby adom
The hero-image lab with a human gate: `hero-studio pose <owner/slug>` opens the studio in front of the human, posed for their package, and blocks until they click Generate, producing the provenance-stamped 2000x1250 PNG the registry's hero gate requires. The image carries the title and artwork; the exported image also bakes the type pill (top right) and full wiki slug (bottom right); the page shows the hero bare, and only the homepage marquee overlays its own dynamic info. Every hero wears its P
state.lint is not a reliable gate: title/showcase collision never fires, and findings go stale across mutations
Field report from a live pose (john/svcwatch, 2026-08-28, hero-studio 0.20.3, Linux container, surface webview). Two problems with state.lint as a gate for a driving AI. The human caught the defect the linter is documented to catch.
1. The title/showcase collision check never fired
The skill documents the layout linter as checking "TEXT COLLIDING WITH THE SHOWCASE (the fix is ALWAYS to move or resize the showcase, never to shrink the title)".
Repro: headline "Service Containers", layout bleed-bottom, showcase loaded from a real screenshot via POST /showcase-file, fontSize.title 8 cqw. At that size the word "Containers" visibly overlaps the left edge of the showcase window in the composed PNG (confirmed by hero-studio compose and by eye).
GET /state at that exact moment returned:
lint: [{"id":"headroom","level":"info","msg":"published headroom: 498px between the lowest image-zone text and the marquee band at export scale (2000x1250)"}]
flags: {"title": false, "subtitle": false, "showcase": false, "type": false}
No collision finding, no flag, Generate unlocked. The human reviewing the gate spotted the overlap and asked "does hero studio tell you this automatically to warn you?" The answer was no.
2. state.lint goes stale, so a driving AI reads findings for a pose that no longer exists
Through most of the session state.lint kept returning the findings from the ORIGINAL pose:
warn subtitle extends 5px into the dynamic marquee band
warn title wraps to 4 lines; shorten it or lower the title size
info published headroom: -93px
These persisted verbatim across: a headline change, a subtitle change, two fit calls, and a full showcase replacement. The headroom figure stayed at -93px long after the real value had changed (it later jumped to +498px in one step once a browser surface finally re-rendered).
This follows from lint being computed by the open studio page rather than the server, which the skill does document. But the consequence is not documented and is severe for the driving AI: there is no way to tell a FRESH all-clear from a STALE all-clear. Polling state.lint after set looks like it succeeded, and silently is not measuring the current pose. fit has the same dependency: two fit calls returned "solving in the open surface" and fontSize.title never moved.
Asks
- Make the collision check actually fire for the composed title vs an imported showcase, or say in
state.lintthat it cannot see that geometry (the full-import caveat pattern already used elsewhere is the right precedent). - Give lint findings a freshness marker: a
computedAttimestamp and the pose revision they were computed against, so a driving AI can refuse to trust findings older than its last mutation. An explicitstale: truebeats a plausible-looking empty array. - Consider computing the measurable checks server-side during
compose, which already renders headlessly and is the only path that reliably reflects the current pose.
Why this matters
The skill tells the driving AI to "fix warns without being asked" and treats state.lint as the machine-readable gate. Today that gate can report all-clear on a pose with a visible collision, and can report warnings for a pose that was replaced ten minutes ago. Both failure modes push the catch back onto the human at the gate, which is the thing the linter exists to prevent.