mcpbeat

Doc Screenshots

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".

9k tokens
context cost
the whole folder, loaded on every use
7
files
ships runnable scripts
0
copies elsewhere
how many repositories repackaged it
1967
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/WordPress/wordpress-playground --skill doc-screenshots

What comes with it

27 449 bytes besides the instruction
evals/evals.json
evals/files/boxes.json
evals/files/broken-config.json
examples/config-overview-cards.json
examples/config-walkthrough-arrows.json
scripts/annotate.py

The instruction itself

6 sections, as written by the author

Documentation Screenshot Annotations

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.

Workflow

  • Capture. Take screenshots at deviceScaleFactor: 2. Never eyeball

coordinates: 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.

  • Author the config. All geometry is in CSS px relative to the

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.

  • Quality gate — actually look. Read the rendered WEBP at full size,

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.

Choosing the annotation mode

  • Pointing at specific controls (a dialog walkthrough, "click here"):

use outlines + free arrows. No dim, no cards. One arrow per target,

tail starting from empty space, arriving straight onto the outline.

  • Overview with a legend ("these are the three persistence controls"):

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.

  • Add chrome_bar (44px bar, traffic lights, URL pill) only when browser

context matters to the reader.

Placement rules the script cannot decide for you

  • Arrow endpoints: the tip must stop 5–7px short of the target's outline

(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.

  • Arrows must never cross each other or overlap another annotation. If a

layout forces a crossing, move the tail, flip the axis, or reorder cards

so each card sits roughly above its target.

  • Outlines must enclose the whole control including secondary lines (a

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.

  • Card copy: title 2–3 words, subtitle one short clause. The script warns

on stderr if text overflows its card — treat that as a hard failure.

Style constants (already baked into the script — do not override)

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.

Repo layout

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.

How to use it

Copy the folder

Take wordpress/doc-screenshots from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

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.

Install what it needs

The instructions reference pip. Without those the skill loads but fails at the first command.