wordpress/doc-screenshots
Annotate UI screenshots with documentation callouts in Fellyph's established visual style — uniform-width orange arrows with white halos, double-stroke target outlines, numbered callout cards, dim overlays and a framed canvas. Use this whenever the user asks to annotate a screenshot, add arrows or callouts to a screenshot, create documentation images, highlight UI controls in a capture, or produce docs/tutorial visuals for Playground, Studio or any web UI — even if they just say "add arrows to this" or "make a docs screenshot".
npx skills add https://github.com/WordPress/wordpress-playground --skill doc-screenshots
Produce annotated UI screenshots in one specific house style: uniform-width
orange (#e8590c) arrows with white halos, double-stroke blue outlines around
targets, and (for overviews) a row of numbered callout cards over a dimmed
screenshot. Never use stock arrow shapes, stroked polylines, or ad-hoc styles.
The geometry engine lives in scripts/annotate.py. Your job is to produce
accurate coordinates and a config JSON; the script renders everything
(supersampling, Bézier ribbons, halos, cards, shadows, WEBP export) exactly
to spec. Do not reimplement the drawing by hand.
deviceScaleFactor: 2. Never eyeballcoordinates: record every target's bounding box programmatically and save
the boxes to JSON — including _regions_ (panels, sidebars, block trees),
not just buttons; eyeballed region outlines are the most common
quality-gate failure. With Playwright:
const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 2 });
// ... navigate, prepare UI state ...
const box = await page.locator('button:has-text("Export")').boundingBox();
await page.screenshot({ path: 'shot.png' });
Iframes: Playwright's locator(...).boundingBox() already returns
main-viewport coordinates, even inside nested iframes (Playground nests
main page → remote.html wrapper → the WordPress scope frame) — use
the boxes as-is, no offsets. Only raw getBoundingClientRect() inside a
frame's own evaluate() (or the Chrome DevTools MCP tools) needs the
enclosing iframe's box offset added.
WordPress modals: editor screens open welcome guides ("Edit your
site" → Get started) whose overlay swallows clicks; some have no
aria-label="Close" button. Dismiss with an Escape loop — while
.components-modal__screen-overlay exists, press Escape on the frame's
body, wait ~1s — and retry the blocked click between attempts. The modal
can appear _after_ the page looks loaded, so dismiss lazily around the
click, not once up front.
Prefer driving the browser from Node with the repo's own
node_modules/playwright; otherwise `pip install playwright &&
playwright install chromium`, or use the Chrome DevTools MCP capture
tools.
Before capturing, clean up dev-environment artifacts: update nags, debug
badges, plugin notices. They must not appear in docs imagery.
screenshot's top-left. Write a config JSON (schema in the script's
docstring — read it; runnable examples of both modes are in examples/,
sharing the bundled sample-shot.webp) and run:
python .agents/skills/doc-screenshots/scripts/annotate.py config.json --crops crops/
The script needs Python with Pillow. If no suitable interpreter is
active, create a venv in the session scratchpad (`python3 -m venv
<scratchpad>/venv && <scratchpad>/venv/bin/pip install Pillow`) and call
that interpreter directly. The script validates the config up front and
exits with a readable config error:message on bad input;output must be a.webp path.
plus the zoomed crops the script saves of every arrowhead and outline
(named <output-stem>-NN-<spot>.png, so one crops dir can serve all
configs of a batch).
Check: tip gaps even (5–7px short of each outline), halos unbroken,
no arrow crosses another arrow or a sibling annotation, no card text
overflow warnings on stderr, artifacts removed. Also sanity-check
legibility at docs width (~860px) and mobile (~343px) — if labels become
unreadable, simplify rather than shrink. Fix and re-render until clean.
use outlines + free arrows. No dim, no cards. One arrow per target,
tail starting from empty space, arriving straight onto the outline.
use cards + outlines + dim. Cards get target indexes and the
script auto-draws vertical arrows from each card's bottom edge onto its
outline. Set dim.region to the area holding the documented controls so
they stay at full brightness while the rest dims 24% toward #28313b.
chrome_bar (44px bar, traffic lights, URL pill) only when browsercontext matters to the reader.
(for card arrows the script handles the 6px gap; for free arrows, place
to accordingly). Both tangents are axis-parallel — pick axis so the
arrow leaves and arrives straight, giving the calm S-curve.
layout forces a crossing, move the tail, flip the axis, or reorder cards
so each card sits roughly above its target.
row's timestamp, a button's icon), not just the text node you queried
for. Pad 6–10px, radius 10–14 for rounded rects; plain circles r≈25 for
icon-only buttons.
on stderr if text overflows its card — treat that as a hard failure.
Arrows are orange #e8590c on pure white halos; outlines, badges and cards stay blue #3858e9. The shaft is a uniform 9px line (constant
top to bottom, round caps) ending in an open chevron head — two 16px
diagonal strokes of the same width sweeping back from the tip at ±35°;
halo expanded 3.2px per side. Outline = white width 9 on
the bbox expanded 2.5px, blue width 4 on the exact bbox. Canvas #f6f7f7,
36px margins, rounded frame with 1px #dcdcde border and soft shadow. Cards:
white, radius 16, 1px #ccced0 border, double shadow, blue badge r22,
Helvetica Neue (27px bold title #101517 / 19px subtitle #2c3338). Export is
WEBP quality ~89 after a single LANCZOS downsample from the supersampled
canvas.
Reference outputs in this style: an action walkthrough
(free arrows onto dialog controls) and an overview legend (three cards over
a dimmed page) — match their look, spacing and restraint.
The source of truth is .agents/skills/doc-screenshots/. .claude/skills
is a committed symlink to ../.agents/skills, so Claude Code loads the same
files — edit only under .agents/skills/ and never create a separate copy.
Take wordpress/doc-screenshots from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference pip.
Without those the skill loads but fails at the first command.