| name | mock-markup |
| description | Markup from a mock image or screenshot — recreate a page as HTML/CSS when the only input is an exported design image (Figma export, retina screenshot, competitor-site capture) with no reference HTML. Normalizes @2x/@3x exports to CSS pixels (scan mock), then drives the deterministic verify-markup loop to a DONE verdict. Works with any agent model — the agent's own vision is the only VLM required, no API key; Haiku handles structure/spacing cheaply, hard 1px endgames measured to need Sonnet (see Model selection). Use when asked to "implement this mock", "turn this screenshot into HTML", or markup from an image that did not come from this repo's own capture tooling. |
| metadata | {"internal":true} |
Mock Markup — image in, verified page out
The auto-markup / dynamic-markup loops assume targets captured by this
repo's own tooling: @1x, pixel width = CSS viewport width, and a
reference page that proves the 0/0 floor. A mock arrives with none of
that. This mode closes the three gaps in order: normalize the image,
transcribe what it shows, loop on the verdict.
Phase 0 — intake (once per image)
vlmkit scan mock mock.png --out targets/target-desktop.png
- Infers the device-pixel scale (@2x Figma exports, @3x phone shots) by
matching against common CSS viewport widths, and writes the
normalized @1x PNG. Loop against the normalized file, never the
raw export — a 2560px-wide retina image renders the attempt at a
2560px viewport and nothing converges.
- If the report says the scale is ambiguous, resolve it yourself
(
--width <design px> when the design width is known; Figma frames
are usually 1440 or 1280).
- If the image is a real screenshot or has JPEG history (browser
screenshot, exported-then-recompressed asset, anything not a pristine
design-tool PNG), add
--capture real. This writes a sidecar that
makes verify markup use degraded-capture tolerances — compression
smears small text, so sub-fragment composition and pixel-presence
need looser floors (a pixel-perfect page fails 0/0 against its own
JPEG screenshot without this). Capture quality is declared, not
auto-detected — you are the one who knows the file's history.
- If the report says noisy (component count above ~24): the mock is
photo-heavy or dense. Build the page skeleton with the page loop, but
drive each busy region separately — crop it and use
build component
— instead of expecting page-level 0/0 in one pass.
- Multiple images at different widths = responsive variants: normalize
each, and treat the widths as the breakpoint spec (dynamic-markup
Phase A rules apply).
Phase 1 — transcription (once)
Read the normalized image ONCE with your own vision and take thorough
notes: structure, sizes (measure — sizes are spec), colors, and the
complete copy. Real text from the mock, transcribed exactly; there is
no reference page to fall back on, and check copy's placeholder scan
treats lorem-ipsum-style filler as a suspect. Transcription of small
(~14px) text is a measured error source — proper nouns, years, and
·-style separators get silently mangled and no pixel gate will catch
it. Zoom into a crop before transcribing fine print, and if the
requester can supply a copy manifest, ask for one and verify with
check copy --manifest. Manifest matching sweeps disclosure states
(closed <details>, unselected tabs, aria-expanded=false) — copy
inside a collapsed panel passes with provenance, so keep disclosures
in their spec'd default state; never open them just to satisfy the
gate. Matching runs against the VISIBLY rendered text: copy that a
user cannot actually see — font-size:0 / opacity:0 / transparent
color, off-screen positioning, text-indent:-9999px, transforms,
clip/clip-path:inset, zero-size overflow boxes, same-color
camouflage, or sr-only-style visually-hidden text — is reported as
copy-invisible with a reason class, never as satisfied. Render the
line visibly instead of hiding it, and keep assistive-tech-only
strings out of the manifest (it is the user-visible copy spec). When
an invisibility is genuinely deliberate, the requester can accept
that class with --allow-invisible <class> (e.g. visually-hidden
for sr-only) — accepted lines stay listed with their reason. Manifest markdown
headings (# Section) are organizing comments, not required lines.
Note text-transform counts: if CSS uppercases a heading, the user
reads the uppercase form, and a mixed-case manifest line will not
match it. Either way, once composition converges, run
the pixel-side copy check:
vlmkit check copy attempt.html --target targets/target-desktop.png
It crops every rendered text block's bbox out of the target image into
contact sheets plus a worksheet of expected strings. Flag any
character difference — this is the gate that catches Phase-1
transcription errors. The sheets must be read by a different reader
than the transcriber: pass --vlm (API key), or hand the sheet
paths + worksheet to the driver/verifier in your report. Reviewing
your own transcription with your own eyes is measured to fail — the
S9-fresh run misread Imlil as Imili at transcription, misread the
review crop the same way, and reported the copy gate as PASSED while
the typo survived. If no second reader is available, say so in the
report instead of claiming the gate passed. Photos and illustrations
inside the mock are content, not layout: reproduce their box (size,
position, fill approximation or gradient), never try to redraw them.
Phase 2 — the loop
vlmkit verify markup attempt.html --target targets/target-desktop.png [...]
Same discipline as the fixture flows (one fix per round, obey the
trend line, fix the first kickback item first), with two mock-specific
caveats:
- No
--reference, so no calibration floor. Kickback items are
still real by default. A residual may be declared design-tool
rendering (and reported instead of fixed) ONLY when ALL of these
hold — the S7 run abused this clause to rationalize plain CSS bugs,
so the bar is deliberately high:
- it is ≤2px thin OR sits inside an embedded photo/illustration
region the intake flagged as noisy;
- you re-read the actual crop and name the pixel evidence (colors,
alpha gradient) in your report — "survived my fixes" is not
evidence;
- it is NOT a solid-fill component ≥8px tall, NOT a page-height
delta, and NOT accompanied by gap/ordering items in the same
region — those are always your CSS.
A page-height error is never an artifact: it is unclosed spacing,
and everything below the first wrong gap is its debris. Fix the
gap items before touching anything the height error displaced.
- Pixel diff % is advisory. Font rasterization differs between the
design tool and Chromium, so expect a higher floor than
capture-vs-capture fixtures. Composition 0/0 + height within
tolerance is the gate; the diff number is for trend, not for judging
done.
Model selection
Measured on this exact task type (S7-fresh A/B, same prompt and
budget): Sonnet reached DONE autonomously in 9 rounds (~6x the cost
per run); Haiku 4.5 stalled NOT DONE at 12 rounds on the 1px-divider
endgame (~6x cheaper, ~3x faster). Mock work leans harder on the
endgame skills (hairline placement, text-tone matching against
design-tool rasterization) than fixture work does, so the model gap
matters more here:
- Default to Sonnet for a one-off "turn this mock into a page"
request that must ship without supervision.
- Use Haiku + driver handoffs when batching many mocks behind a
verifier harness and cost dominates; expect to finish hard pages
with escalation legs. Full guidance and the escalation pattern:
dynamic-markup SKILL.md § Model selection.
Done condition
verify markup prints DONE (all targets 0/0 + height in tolerance
- gates clean or expected-warn-only), the
check copy --target sheets
have been reviewed with zero unexplained character differences, and
your report includes the transcribed-copy note and any
declared-irreproducible residuals with their reasoning. If the intake step flagged the mock as noisy, the
report must say which regions were driven by build component instead.
Budget and KPIs: as in dynamic-markup (rounds ledger-audited via
.vlmkit/run-ledger.jsonl).