plugins/stardust/skills/diff/SKILL.md
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 Step 10), or for any prototype↔build fidelity check; invocable as stardust:diff and from workflows.
npx skillsauth add adobe/skills diffInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
stardust:deploy skill's Step 10) — use --profile eds.--profile generic.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).
| 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) | geometry / layout regressions |
content-diff extracts an ordered, role-classified inventory (heading / eyebrow /
cta+href / body) from each <main>, classifying by computed style + tag so the
prototype's DOM and the built DOM compare symmetrically, then diffs them.
# Prereq 0: playwright importable from the project root — probe
# node -e "import('playwright').then(()=>process.exit(0))"
# and re-install (npm i -D playwright --no-save --legacy-peer-deps) on failure:
# a --no-save install from extract is PRUNED by any later real npm i
# (extract SKILL.md § Setup). Run the copied scripts from the project, not the plugin.
# 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.
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> (content root; content-diff defaults from the profile, visual-diff to main),
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:
networkidle (local prototypes / harnesses, unchanged);.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);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.
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.Pass bar: visual red flags none/justified AND content-diff 0 structural 🔴 (🟡/🟠 confirmed intended). Re-run BOTH after each fix.
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 + stardust:deploy finding numbers.generic — neutral source/build language for any stack.Add a profile by copying generic in diff-profiles.mjs and editing hints.
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 stardust:deploy gates:
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.
Call both scripts in a validation phase and gate on the output. The
stardust:deploy conversion workflow's Validate phase runs both after building
a local harness; mirror that:
visual-diff … --profile eds → fix STRETCHED/FLUSH-LEFT/SURFACE-GROUND/GAP flags (unless justified).content-diff … --profile eds → fix every 🔴; confirm 🟡/🟠.Naming note: this skill ships in the
stardustplugin and is invoked asstardust:diff. It pairs withstardust:deploy, whose Step 10 runs both probes as its Validate gate.
tools
Use the run-workflow MCP to discover, compose, execute, publish, and save Adobe Firefly workflows. TRIGGER when: user asks what actions are available, what the MCP can do, how to process images/video/3D via workflow, wants to build/run/save/publish a workflow, OR pastes any workflow/batch/execution ID. BARE ID (UUID/workflowId/batchId) = INSPECT ONLY — call inspect_run, NEVER run_workflow_submit. ALWAYS call list_actions first for capability/discovery questions. DO NOT TRIGGER for direct Firefly API calls without MCP (use firefly-api-specs).
tools
Run predefined featured workflows via run-workflow MCP. TRIGGER when user names a featured workflow (retargeting, banners at scale, localization, packaging, banner advertising, etc.) or asks to run a known marketing/production workflow. Requires run-workflow MCP. ALWAYS call get_featured_workflow before compose_workflow. DO NOT TRIGGER for custom one-off workflows with no named template — use run-workflow skill.
tools
Migrate an Adobe Commerce App Builder project from the Integration Starter Kit or Checkout Starter Kit to the new App Management approach. Run from the root of the App Builder project to be migrated. Pass --auto to skip confirmation prompts (suitable for CI or batch use) — auto mode prints a summary of all Q&A questions answered with their defaults. Pass --doc-scan-only to scan README.md and env.dist for outdated content without modifying any files. Use when the user wants to migrate an App Builder project from the Integration Starter Kit or Checkout Starter Kit to the App Management approach, or mentions upgrading their Adobe Commerce extension architecture.
development
Add or modify webhook interceptors in an Adobe Commerce app. Use when the user wants to intercept Commerce operations to validate input, append data, or modify behavior — before or after execution. Requires a base app initialized with commerce-app-init.