diff
This skill compares a prototype web page against its built version using two complementary Playwright-based probes: a pixel/layout diff detecting stretched…
Identity proven by GitHub's verified organisation badge (adobe)
- Agents
- Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode
- License
- Apache-2.0 (permissive)
- Pin
- 940b879pinned Oct 9, 2026
- Context cost
- about 3,500 tokenswhen the skill loads
- GitHub stars
- 197on adobe/skills
- Scripts
- 13 scripts
What it does
“Reconcile a converted/built web page against its source prototype with two complementary probes — a PIXEL/layout diff (stretched images, dropped wraps, blank renders, colour flips) and a STRUCTURAL content+typography diff (dropped/mis-slotted headings, eyebrows, CTAs; rendered-face font forks). Stack-agnostic via profiles (eds | generic). Use after converting a prototype to EDS/AEM (the stardust `deploy` skill Step 10), or for any prototype↔build fidelity check; invocable as the stardust `diff` skill and from workflows.”
Generated summary from the skill's own text
This skill compares a prototype web page against its built version using two complementary Playwright-based probes: a pixel/layout diff detecting stretched images, dropped wraps, and color flips; and a structural content diff catching missing or misplaced headings, CTAs, body copy, and typography changes. Use it after converting a prototype to EDS or for any fidelity check between two rendered URLs, with stack-specific behavior controlled by profiles.
When to use it
- After converting a prototype to EDS or AEM
- Validating design fidelity between Figma export and React build
- Checking prototype versus rebuilt page layouts
- As a QA gate in conversion or deployment workflows
When not to
- For static HTML files without JavaScript decoration
- When you need pixel-perfect color or geometry comparisons
- If prototype and build use fundamentally different DOM structures
Install
These commands install from the publisher's repository and need no account.
skills CLI
npx skills add adobe/skills --skill diffInstalls from the repository's default branch, which may have changed since the commit we checked.
GitHub CLI 2.90 or later
gh skill install adobe/skills diff@940b8795c0df3e25de68ce3881dc90855129b215Installs the commit we checked (940b879).
Copy by hand
Download the folder at 940b879 and put it in .claude/skills/diff for Claude Code and in .agents/skills/diff for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode.
Claude Code plugin
/plugin install adobe-for-creativity@claude-plugins-officialThe publisher's plugin in Claude's official marketplace. It may hold more than this skill, and we have not pinned it.
Copy for your agent
A prompt for any coding agent. It installs this skill from GitHub at the pinned commit, copies the files as they are, and runs nothing unless you ask.
Prompt
Install the agent skill "diff" by Adobe, Inc.. Source, pinned to commit 940b8795c0df3e25de68ce3881dc90855129b215: https://github.com/adobe/skills/tree/940b8795c0df3e25de68ce3881dc90855129b215/plugins/stardust/skills/diff 1. Download that folder at exactly this commit. Do not use the default branch. 2. Copy every file unchanged, keeping the folder structure, into .claude/skills/diff/ for Claude Code and .agents/skills/diff/ for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode. Create the folders if they do not exist. 3. Do not edit the files, and do not run any script in the folder unless I ask you to. 4. When you are done, list the files you copied and where you put them. Listing and checks: https://www.agentnative.dev/skills/adobe-inc/diff
SKILL.md at the pin
---
name: diff
description: Reconcile a converted/built web page against its source prototype with two complementary probes — a PIXEL/layout diff (stretched images, dropped wraps, blank renders, colour flips) and a STRUCTURAL content+typography diff (dropped/mis-slotted headings, eyebrows, CTAs; rendered-face font forks). Stack-agnostic via profiles (eds | generic). Use after converting a prototype to EDS/AEM (the stardust `deploy` skill Step 10), or for any prototype↔build fidelity check; invocable as the stardust `diff` skill and from workflows.
license: Apache-2.0
compatibility: Requires Node 22+, Playwright with Chromium resolvable from the project, playwright-cli on PATH, and the impeccable skill (github.com/pbakaus/impeccable) installed alongside stardust.
---
# stardust:diff — prototype ↔ build reconcile
Two probes that compare a **source** prototype against a **built** page. They catch
**disjoint** failure classes — run BOTH; either alone gives a false "looks fine".
Both are framework-agnostic Playwright probes that compare two rendered URLs by
**computed style + DOM** (not pixels). All stack-specific language lives in a
**profile** (`--profile eds|generic`); the comparison logic is generic.
## When to use
- After converting a prototype to EDS (the stardust `deploy` skill's Step 10) — use `--profile eds`.
- Any "does the build match the design?" check between two rendered URLs (a Figma export vs a React build, a legacy page vs a rebuild) — use `--profile generic`.
- Inside a conversion/QA workflow as the validation gate (see *Workflow use*).
Not for: a single static file with no JS decoration (use the build/harness URL so components are decorated — a raw `.plain.html` has no roles to classify).
## The two probes
| Probe | Script | Sees | Blind to |
|---|---|---|---|
| **Pixel / layout** | `skills/diff/scripts/visual-diff.mjs` | stretched images, dropped max-width wraps, blank renders, surface/ground colour flips, image-count gaps | "right text, wrong slot"; a dropped CTA (full pixels, plausible colours → no flag) |
| **Structural content + type** | `skills/diff/scripts/content-diff.mjs` | MISSING / ROLE-SWAPPED headings·eyebrows·CTAs, invented/dropped body copy, rendered-FACE font forks (width probe); dropped `placeholder`/`aria-label`/`title` values, missing/wrong/moved icons — on the main root AND the `header`/`footer` chrome roots by default | geometry / layout regressions |
`content-diff` extracts an ordered, role-classified inventory (`heading` / `eyebrow` /
`cta`+href / `body`) from each root — the `--main` root(s) plus, by default, the `header`
and `footer` chrome roots — classifying by **computed style + tag** so the prototype's DOM
and the built DOM compare symmetrically, then diffs them root by root; a second inventory per
root carries the `placeholder` / `aria-label` / `title` values and the icons.
## Run it
```bash
# Prereq 0: playwright importable from the project root — probe
# node -e "import('playwright').then(()=>process.exit(0))"
# and on failure install it AS A devDependency (npm i -D playwright pixelmatch pngjs cheerio
# --legacy-peer-deps — never --no-save: a --no-save install is PRUNED by the next real npm i,
# recorded twice in one run; extract SKILL.md § Setup). Run the copied scripts from the project
# root, not the plugin: ESM resolves `playwright` from the script's own location.
# Copy the WHOLE skills/diff/scripts/ dir: content-diff imports its local diff-profiles.mjs
# AND content-inventory.mjs. (The deploy gates #93/#94 now use their OWN synced copies in
# skills/deploy/scripts/ — A6/A2 are independent of this skill; the two copies must stay in
# sync until the diff-skill abrasion PR consolidates them.)
# Prereq: a RENDERABLE source. Static → serve from its own dir (python3 -m http.server).
# The build URL must be the DECORATED page (live/preview or a local harness), not raw markup.
# ONE server, ONE port — probe before starting one (curl is always present, lsof is not):
curl -sI localhost:8791/ | head -1 # 200/404 = something serves the port; no line = free
curl -sI localhost:8791/<prototype>.html | head -1 # 200 = it serves YOUR dir: reuse it
command -v lsof >/dev/null && lsof -nP -iTCP:8791 -sTCP:LISTEN # optional: names the pid
# Nothing answered → start yours. Answers but not your file → a foreign server: never kill
# a listener you did not start; prefer a per-project port — a stale server from another
# project makes both probes measure a foreign page. `lsof … || echo free` is not a probe —
# without lsof it prints "free" beside a live listener (recorded: a second server on the
# same port died at once and the round chased 404s).
PROTO="http://localhost:8791/<prototype>.html"
BUILD="https://<branch>--<repo>--<owner>.aem.page/<path>" # or http://localhost:3000/<harness>
# 1. PIXEL/layout
node skills/diff/scripts/visual-diff.mjs "$PROTO" "$BUILD" --profile eds --sections ".hero"
# 2. STRUCTURAL content + type
node skills/diff/scripts/content-diff.mjs "$PROTO" "$BUILD" --profile eds # --json dumps both inventories
```
Flags (both tools): `--profile eds|generic` (default `eds`), `--width <px>` (default 1280),
`--main <selector[,selector…]>` (content root; content-diff defaults from the profile and takes a
comma-separated list — every root is inventoried and diffed on its own and each finding line
carries its root as `[root]`; visual-diff takes one selector, default `main`), content-diff only:
`--chrome` / `--no-chrome` (default on — the `header` and `footer` roots, resolved as the first
`<header>`/`[role=banner]` and `<footer>`/`[role=contentinfo]` outside the main root(s), are
compared beside it; a side lacking one is measured as empty there and the root line says so),
plus the live-target set (shared engine: `scripts/live-session.mjs` — every context sends the
real-Chrome UA **and** the standard Chrome request headers; the UA alone still 403s on
Akamai-class bot management):
- `--ua <string>` — user agent override (default: real-Chrome desktop UA).
- `--wait-until <state>` — goto wait override. Default rule (one shared
`defaultWaitUntil` in `scripts/live-session.mjs`), decided **per URL side**, three tiers:
- localhost/127.0.0.1 → `networkidle` (local prototypes / harnesses, unchanged);
- EDS build/preview origins — hostnames ending in `.aem.page`, `.aem.live`, `.hlx.page`,
`.hlx.live` → `networkidle` (they decorate asynchronously and reliably reach
networkidle; measuring at domcontentloaded reads the pre-decoration DOM — flaky
false reds / FONT FORK on deploy Step 10);
- all other live http(s) → `domcontentloaded` (live sites with analytics beacons
never reach networkidle).
`--wait-until` overrides all three tiers.
- `--dismiss [sel,...]` — dismiss overlays on both sides: cookie consent (clicked, not
removed) AND timed marketing/newsletter modals, plus optional extra site-specific
selectors; the mouse is parked afterwards.
- `--headed` — escalation for bot-managed sites: headed stealth real Chrome.
- `--locale <tag>` — pin Accept-Language + context locale (geo-redirecting sites capture a
different locale per run otherwise).
`visual-diff` also: `--out <dir>`, `--sections a,b` (per-section screenshots).
A bot-management challenge/blocked interstitial on either navigation fails LOUD with
**exit 3** — it is never measured as the source. Escalate with `--headed`; if that is still
blocked, the site needs crawl.mjs-class capture and the check cannot run headless.
A plain (non-challenge) HTTP error on either side — e.g. a **404 build side, normal on
aem.page before preview propagation** — is NOT fatal: the probe logs a loud warning,
measures the error page, and the flags (BLANK RENDER / content asymmetry) carry the
signal with **exit 0**. That is the probes' advisory contract: 0 = ran (flags advisory),
1 = probe error, 3 = bot challenge.
## The published-origin probes (#125)
`content-diff` reconciles a prototype with its build; against a LIVE commerce origin its per-node
findings were false, and the pixel gate passed a page whose every card was clipped
(`../replica/reference/source-fidelity-gate.md` § The all-pages published-origin gate). Three
probes ask the checkable questions; all load both sides in the same window-free real-Chrome tier
and settle them the same way (`scripts/measure-live.mjs`).
```bash
ORIGIN="https://www.example.com/<path>"; SERVED="https://main--repo--owner.aem.live/<path>"
node stardust/scripts/diff/clip-probe.mjs "$SERVED" [--json clip.json] # D1: exit 2 on cut / hidden text or controls
node stardust/scripts/diff/content-presence.mjs "$ORIGIN" "$SERVED" [--variable "<selO>=<selE>"] # D2: exit 2 on MISSING/HIDDEN link or heading
node stardust/scripts/diff/unit-geometry.mjs "$ORIGIN" "$SERVED" --unit "<selO>=<selE>" --n 2 # D3: exit 2 on an element off / hidden / missing
```
Reading: a 🔴 clip group names the clipper (a fixed-height box with `overflow: hidden`) — fix
that block's CSS, never the content. HIDDEN LINK = in the DOM but clipped (CSS); MISSING LINK =
not served (encoder); CONTROL STATE = same control, another value; COUNT … = session-variable
region, confirm by eye. The origin side fails loud on HTTP ≥ 400 (exit 4) and a bot challenge
(exit 3). Traps: an infinite-scroll origin keeps loading under the settle — mark the region
`--variable`; a live origin without `<main>` compares whole page against whole page (the scope
line says so), header / footer left to the chrome crop gate unless `--chrome`; the count-phrase
and "read more" heuristics are English word lists (`--count-words`, `--more-words`).
## Reading content-diff
- 🔴 **MISSING CTA / HEADING / EYEBROW** — real dropped content. FIX. A missing eyebrow is most often a segmentation drop where the eyebrow precedes its heading; a missing CTA means the component never rendered the link. These are exactly what the pixel probe cannot see.
- 🔴 **ROLE SWAP** — same text under a different role (body painted as eyebrow, eyebrow folded into a teaser). FIX the component's node segmentation.
- 🔴 **MISSING PLACEHOLDER / ARIA-LABEL, MISSING ICON, ICON DIFF on an interactive element** (an input, a button, a link or inside one) — a dropped localized search placeholder, a share link without its aria-label or icon, a wrong flag in a locale link, an empty icon box. FIX. Outside interactive elements the same findings are 🟡.
- 🟡 **MISSING TITLE** — always 🟡: a title tooltip is not read by most assistive tech and is commonly dropped by design. 🟡 **ICON MOVED** — the same icon (glyph code point / file name / svg signature) at another anchor, paired by identity and document order after the anchor passes (typically a text-less `<button>` host that anchors as `button#n`): the icon is present, CONFIRM its placement. 🟡 **ICON KIND** (glyph vs svg vs image — the pixel probe judges) and every **EXTRA**.
- Every finding line names its root — `🔴 MISSING PLACEHOLDER [header]: …` — and the report prints one block per root (`root "main"`, `root "header"`, …; a root a side lacks is measured as empty there and the line says which side).
- 🟡 **MISSING BODY / EXTRA** — body prose dropped, or build copy with no source. Usually a placeholder→real-copy rewrite. CONFIRM intended; don't blindly "fix".
- 🟠 **FONT FORK** — matched lines whose rendered FACE differs (width probe, never `document.fonts.check`). `source X→sys` means the prototype named font X but never loaded it and fell back to system — the build self-hosting the intended fallback is then CORRECT, not a bug. All forked lines are grouped into one advisory.
- **Known limitation — node-granularity JOIN/SPLIT reads as 🔴 (#87).** When the source renders one text run as N sibling nodes and the build renders the same text as ONE node (or vice versa — e.g. three fact chips vs one combined chip span), the diff currently reports MISSING + ROLE SWAP + EXTRA for what is a non-defect. Until concat-matching lands (a source node that is a substring of a same-region build node → 🟡 JOIN/SPLIT advisory), verify a 🔴 whose texts concatenate into an EXTRA finding's text before treating it as dropped content — confirmed-justified is a pass.
**Pass bar:** visual red flags none/justified **AND** content-diff **0 structural 🔴** (🟡/🟠 confirmed intended). Re-run BOTH after each fix.
## Profiles
`skills/diff/scripts/diff-profiles.mjs` holds them. A profile supplies the source/target **labels**,
per-flag **remediation hints**, the **font-delta** threshold, the default content-root
**selector**, and the **eyebrow** classifier thresholds. The engines carry no stack
strings.
- **`eds`** (default) — Edge Delivery / DA remediation language + the stardust `deploy` skill's finding numbers.
- **`generic`** — neutral source/build language for any stack.
Add a profile by copying `generic` in `diff-profiles.mjs` and editing `hints`.
## Shared engine + the in-loop sibling
The structural probe's classifier + differ live in `skills/diff/scripts/content-inventory.mjs`
(and a synced copy in `skills/deploy/scripts/content-inventory.mjs` that the deploy gates import
locally so they don't depend on this skill — keep the two copies in sync until consolidated).
They measure with the same instrument as two gates of the stardust `deploy` skill:
`section-schema.mjs` (the pre-code ENCODE/DECODE contract, deploy #93) and `block-roundtrip.mjs`
(the in-loop per-block gate, deploy #94 — the same inventory diff, run per block at authoring time
against a local decorate() harness, no DA needed, exit-code gated). Run the in-loop gate while
converting; run THIS skill's two probes as the final post-deploy proof. A defect first found here
that the in-loop gate passed = the delivery pipeline reshaped the content in transport — fix the
block's flattened-shape fallback, not the authoring.
## Workflow use
Call both scripts in a validation phase and gate on the output. The
the stardust `deploy` skill's conversion workflow Validate phase runs both after building
a local harness; mirror that:
1. Build/serve the decorated build page (e.g. a local QA harness, or the branch preview).
2. `visual-diff … --profile eds` → fix STRETCHED/FLUSH-LEFT/SURFACE-GROUND/GAP flags (unless justified).
3. `content-diff … --profile eds` → fix every 🔴; confirm 🟡/🟠.
4. Loop until visual none/justified AND content-diff 0 structural 🔴.
> Naming note: this skill ships in the `stardust` plugin and is invoked as
> the stardust `diff` skill. It pairs with the stardust `deploy` skill, whose Step 10 runs both probes
> as its Validate gate.
From adobe/skills at 940b879, shown as plain text under its license: Apache-2.0 (permissive). The license text is in plugins/stardust/skills/diff/SKILL.md.
View SKILL.md on GitHub at 940b879Files
- SKILL.md15 KB · 81253fdb0897
- scripts/
- clip-probe.mjs24 KB · 45667fc65c7a
- content-diff.mjs39 KB · 7c718d9bb5cd
- content-inventory.mjs11 KB · 7328c17385c8
- content-presence.mjs32 KB · d104bc77034c
- diff-profiles.mjs6.5 KB · a31b42b30928
- live-session.mjs24 KB · 1768fa5a3465
- measure-live.mjs8.9 KB · 660fdee6f114
- test/
- clip-probe.test.mjs8.7 KB · ed4bd694f54e
- content-diff.test.mjs26 KB · 034c8ea2c417
- content-presence.test.mjs8.2 KB · 2525e761644a
- unit-geometry.test.mjs6.2 KB · f6240cd7b378
- unit-geometry.mjs21 KB · bc3de1611c5c
- visual-diff.mjs21 KB · 89dba9d98271
14 files, 252 KB in total. Each file has a sha256 in the pin's manifest; the first 12 characters are shown.
Skill folder on GitHub at 940b879More from Adobe, Inc.
adobe
This skill provides an onboarding tour of the Adobe for creativity connector, displaying available capabilities, example workflows, and which Adobe product…
Claude Code, Codex, Cursor +4 · about 2.2K tokens · pinned 6 days ago
adobe-anydocx
This skill routes DOCX conversion tasks that have a PDF source to Adobe's pdf_export tool with the appropriate format setting.
Claude Code, Codex, Cursor +4 · about 680 tokens · pinned 6 days ago
adobe-anyexcel
This skill routes PDF-to-Excel conversion tasks to the Adobe pdf_export tool with xlsx target format.
Claude Code, Codex, Cursor +4 · about 690 tokens · pinned 6 days ago
adobe-anypdf
This skill routes PDF tasks to Adobe Acrobat tools for higher fidelity than local Python libraries.
Claude Code, Codex, Cursor +4 · about 2.1K tokens · pinned 6 days ago
adobe-anypptx
This skill routes PowerPoint conversion tasks that have a PDF source to Adobe's pdf_export tool with PPTX output format.
Claude Code, Codex, Cursor +4 · about 670 tokens · pinned 6 days ago
adobe-batch-edit-photos
This skill applies consistent visual adjustments to a set of photos so they appear edited together as a cohesive collection.
Claude Code · about 9.9K tokens · pinned 6 days ago
Related in Testing and QA
next-partial-prefetching-optimizer
This skill optimizes Next.js client-side navigation under Partial Prefetching by defining what UI loads before and after a click.
by VercelOfficial publisher
Claude Code, Codex, Cursor +4 · about 2.8K tokens · pinned 6 days ago
Invalid--Name
This skill cannot be described because its name format is invalid and no functional description or instructions are provided in the skill metadata.
by CrewAIOfficial publisher
Claude Code, Codex, Cursor +4 · about 0 tokens · pinned 6 days ago
chrome-devtools-cli
This skill enables agents to automate browser interactions and inspect web pages using Chrome DevTools via command-line interface.
by GoogleOfficial publisher
Claude Code, Codex, Cursor +4 · about 2.9K tokens · pinned 6 days ago
triaging-visual-review-runs
Inspects PostHog Visual Review screenshot regression runs that gate pull requests, helping engineers triage diff results, identify flaky stories, and decide…
by PostHogOfficial publisher
Claude Code, Codex, Cursor +4 · about 8.9K tokens · pinned 6 days ago
code-change-verification
Runs formatting, linting, type checking, and test verification for SDK code changes after review.
by OpenAIOfficial publisher
Contains scripts
Codex · about 1.3K tokens · pinned 6 days ago
runtime-behavior-probe
This skill guides agents to plan and execute controlled runtime investigations with explicit user approval before each probe runs.
by OpenAIOfficial publisher
Contains scripts
Claude Code, Codex, Cursor +3 · about 3.8K tokens · pinned 6 days ago