Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/stardust/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "stardust",
"description": "Redesign an existing website to make it better. Higher-level guided flow on top of impeccable.",
"version": "0.25.2",
"version": "0.26.0",
"author": {
"name": "Adobe"
},
Expand Down
71 changes: 71 additions & 0 deletions plugins/stardust/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,77 @@ This file starts at 0.14.0. Prior versions (0.3.0 – 0.13.1) are documented in
git history only (plus the branch-scoped notes in
`CHANGELOG-redesign-adobecom.md` and `CHANGELOG-delivery-media-fidelity.md`).

## 0.26.0 — published-origin gate hardened: element-level criteria beside the pixel number (#125)

A 96-page rollout of a pharmacy retailer gated every deployed page on stitched captures and
pixel-compare; its coupon-listing page PASSED at 6.7 % / Δh 69 px while all 284 cards were broken —
a fixed-height card with `overflow: hidden`, the body ~30 px low, the description clipped mid-glyph,
the details link pushed under the primary button, the sort control on another default. The links
were in the served DOM: pure CSS geometry, which pixelmatch underweights (16.5 % over the text
boxes alone). "PASS" meant "right shapes at the right places", not "every element present and
legible". Element presence and legibility are checkable — so the gate checks them. Cross-references
#115 (chrome crop gate), #124 (content-cap row) and the run's feedback items A1–A3.

- **New `replica/scripts/gate-all.mjs` (D0)** — the all-pages published-origin gate: per deployed
page in `state.json`, stitched captures of both sides, `pixel-compare`, then the two DOM probes
below in the same window-free real-Chrome tier; per-page dir + `summary.{json,md}`, `runs/` for
`--only`. **Verdict = pixel % ≤ 10 AND |Δh| ≤ 5 % of origin height AND clipped ≤ 0 (+ documented
allowance) AND content MISSING + HIDDEN links / headings = 0** (+ required repeated units within
4 px); the pixel-only verdict is recorded beside it — every run is calibration data. Sidecars, each
entry documented: `masks.json`, `overrides.json` (shown beside the number, never replacing it),
`clip-allow.json`, `presence.json`, `units.json`. Origin fallback live → previous → crawl shot
(asymmetric, flagged); `--eds-host` gates a code branch; a union (white-padded) pixel metric was
tried as the height guard and rejected (white gaps score as matches) — the guard is explicit.
- **New `diff/scripts/clip-probe.mjs` (D1)** — text lines and controls against every overflow-
clipping ancestor: TEXT CLIPPED / HIDDEN, CONTROL HIDDEN / CLIPPED counted (exit 2); line-clamp,
"read more" collapsibles (`--more-words`), scrollable containers, horizontal cuts advisory;
collapsed menus, sr-only, hidden subtrees, off-page boxes never reported. Emits the visible text
line boxes. Recorded page: 379; the live origin and two known-good pages: 0.
- **New `diff/scripts/content-presence.mjs` (D2)** —
visible headings, links (visible text), buttons, images, text blocks per band (h1–h3 aligned by
LCS) + control state (`--count-words`); MISSING / HIDDEN LINK ×n and HEADING 🔴, buttons and
CONTROL STATE 🟠, MOVED / EXTRA / COUNT / HEADING AS TEXT 🟡; symmetric scope, chrome left to
the crop gate unless `--chrome`, `--variable` regions as counts (HIDDEN still counts), origin
HTTP ≥ 400 fails loud (exit 4). Recorded page: HIDDEN 284, CONTROL STATE 2.
- **New `diff/scripts/unit-geometry.mjs` (D3) + `diff/scripts/measure-live.mjs`** (library) — the shared
settle / session / shadow-DOM measurement lifted from the project measure scripts; per-element
Δx / Δy / Δw / Δh of the first N repeated units relative to the unit, `hidden` where clipped,
origin cached per slug. Recorded card: body +28…+47 px, details link +28 and hidden, badge
70×70 at (−10,−10) vs 64×64 at (−27,−30), card 260 → 238.
- **`pixel-compare.mjs --text-boxes` (D4, auxiliary)**; `textBoxPct` exported. (`--pad`, the rejected
union metric, is not shipped.)
- **The pixel-table rule.** Every crafted prototype and every deployed page is a row:
`gate-all --stage prototype --proto-base <url>` writes `gates/prototypes-<w>/summary.{json,md}` at the
end of Phase 4 (reusing each archetype's cached `live.png`), the default stage writes `gates/all-<w>/`;
`gate-evidence.mjs` reads the tables as the source of record (`--tables`; a page without a row is
`OPEN: no table row`, `--check` names a missing table), the eval requires the prototype table, rollout
Phase H reports both totals. `gate.sh --full` runs clip-probe on the build side in every regime,
content-presence in the published regime, unit-geometry when `stardust/replica/units.json` declares a
repeated-unit family for the slug (`{ family: { origin, build, n, required, pages[], templates[] } }`
— replica recreation-procedure § Repeated-unit families); element lines fail the round and outrank a
pixel PASS in gate-evidence the way the overflow assert does.
- **Coverage carries the verdict; the roster run is a recorded unit** (review notes on the PR).
`update-coverage.mjs --gate <summary.json>` writes each row into `delivery.gate` and flips a failing
page (no documented override) to `failed`; `verify.mjs` never marks a page `verified` while
`delivery.gate.pass` is false — completion derives from one place. Handoff contract § 3: each cluster
subagent runs `gate-all --only` over its own pages inside the fan-out; C-final's roster run is unit
`gate-all` (recorded `running` → `done`, resumable at its boundary) followed by `--gate`. Setup probes
the deps before installing them (a harness that resolves them leaves the code repo untouched).
- **Upstreamed feedback.** A1 `launchStealthHeaded` is WINDOW-FREE by default
(`STARDUST_HEADED_WINDOW=1` opts in). The tier is the BEST AVAILABLE and REGISTERED: Chrome when
installed, else bundled Chromium with a warning, and every probe records `tier` (`chrome` |
`chromium-fallback` | `chromium`) in its evidence; gate-all's summary has a `browser` column and
counts degraded pages. A2 Akamai's HTTP
400 escalation is a challenge marker. A3 setup steps install `playwright pixelmatch pngjs cheerio`
as devDependencies, never `--no-save`. `deploy-batch.mjs` appends the admin `x-error` header to
4xx (the DA 200-images-per-document cap; protocol step 3a').
- **Docs** — gate doc § The all-pages published-origin gate; replica Phase 5 + Setup; deploy
Step 10 item 7; rollout Phase E; diff SKILL § The published-origin probes; scripts index. **Tests**
for the four instruments (pure parts browser-free; clip-probe end-to-end on a fixture where
playwright resolves). Calibration on the recorded roster: pixel-only 66 PASS → full 28 PASS.
- **Open** — default tolerances (`--clip-max 0`, `--unit-tol 4`, 25 % counts), how projects declare
repeated-unit block families, whether buttons / control state should block.

## 0.25.2 — content cap: the container sizing model is measured, persisted and gated at a derived wide width (#124)

A hands-off replica run delivered a site whose live pages centre their content in two nested caps —
Expand Down
1 change: 1 addition & 0 deletions plugins/stardust/evals/lint/script-help.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const EXEMPT = {
'diff/scripts/content-inventory.mjs': 'library: the diff skill\'s copy of the inventory classifier, imported by content-diff; no CLI',
'diff/scripts/diff-profiles.mjs': 'library: the diff skill\'s copy of the stack profiles, imported by content-diff and visual-diff; no CLI',
'diff/scripts/live-session.mjs': 'library: live-site browser session helpers (UA, stealth launch, overlay dismissal); no CLI',
'diff/scripts/measure-live.mjs': 'library: settle + rect/type measurement shared by clip-probe, content-presence, unit-geometry; no CLI',
'dynamics/scripts/lib.mjs': 'library: shared arg/io/playwright helpers for the dynamics instruments; no CLI',
'qa/scripts/lib.mjs': 'library: shared helpers for qa.mjs and its checks; no CLI',
'rollout/scripts/lib.mjs': 'library: shared IO + roll-up helpers for the rollout scripts; no CLI',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
{
"name": "source_fidelity_gate_runs",
"max_score": 20,
"description": "The source-fidelity gate runs all three probes (content-diff and visual-diff with --profile generic against the live URL, plus stitch-shot + pixel-compare) and its evidence (per-iteration metrics, band breakdown, height delta, diff images) is written under stardust/replica/."
"description": "The source-fidelity gate runs all three probes (content-diff and visual-diff with --profile generic against the live URL, plus stitch-shot + pixel-compare) and its evidence (per-iteration metrics, band breakdown, height delta, diff images) is written under stardust/replica/; --full rounds carry the clip-probe line, and every crafted prototype is a row in stardust/replica/gates/prototypes-<width>/summary.json (gate-all --stage prototype) — a prototype without a row is ungated."
},
{
"name": "both_breakpoints_gated",
Expand Down
2 changes: 2 additions & 0 deletions plugins/stardust/evals/replica-source-fidelity/task.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ The stardust `replica` skill is invoked. It:
- "Improving" the design outside the inconsistency register.
- DOM-copying the source page instead of re-authoring.
- Declaring fidelity without gate evidence on disk (metrics, diff artifacts).
- A crafted prototype with no row in `stardust/replica/gates/prototypes-<width>/summary.json`
(the pixel table is the source of record; a sidecar claim alone is not evidence).
- Only gating desktop (mobile is not free — the 360 pass is required).
- Skipping the content-cap row — a container cap wider than 1440 is invisible to both pixel gates.
- Rehosting a licensed brand font.
2 changes: 2 additions & 0 deletions plugins/stardust/skills/deploy/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,8 @@ After deploy, reconcile the EDS page against the source prototype on the **DEPLO

**6. Geometry-fix verification hygiene (#117).** One field "parity verified" claim was wrong three ways at once; when verifying any geometry fix: (a) probe the **rule-bearing element**, not a heuristic match ("white column wider than 400px" happened to select a different box than the one carrying the lifted rule) — pair the element the fixed rule targets on the build with the element whose source rule was lifted on live; (b) verify **serving cache-free** — a reviewer's DevTools showed the old rule at its old line number while both hosts already served the fix; check out-of-band with `curl --compressed <css-url> | grep '<new-rule>'` (block CSS is gzip-encoded — a bare `curl | grep` scans binary and silently matches nothing) and re-render in a **fresh headless context**, telling any human reviewer to hard-refresh before re-judging; (c) a reviewer's screenshot encodes their zoom — **back-compute their CSS viewport** from any element with a known percentage rule (a card at 851px under `width: 50%` → viewport 1702px) and reproduce THAT viewport headlessly; judged at face value their numbers contradicted a correct fix, reproduced at 1702 live and build matched to the pixel.

**7. All-pages published-origin gate at delivery (#125).** Step 10 is per page; once the roster is deployed, run `node stardust/scripts/replica/gate-all.mjs` (project root, through `run-bg.mjs`). DELIVERED = pixel % ≤ 10 AND |Δh| ≤ 5 % AND 0 clipped text / controls AND 0 MISSING / HIDDEN links / headings — a recorded page passed the pixel bar with every card clipped and its links hidden by a fixed-height `overflow: hidden` block. `clip.json` groups name the clipper (fix that block's CSS); `content.json` HIDDEN = CSS, MISSING = encoder. Rules: `../replica/reference/source-fidelity-gate.md` § The all-pages published-origin gate.

The retired `visual-diff` classes are covered elsewhere: stretched images by the `img { height:auto }` reset (#36) + eyeball; dropped max-width wraps and the outer cap by the qa-gate wide pass (#13/#124); blank/broken renders by the computed-style guard; imagery gaps by the `.plain.html` img/alt count (#75) + eyeball.

`content-diff` is stack-agnostic via `skills/deploy/scripts/diff-profiles.mjs` (`--profile eds | generic`); it shares the role classifier with `block-roundtrip`/`section-schema` in `skills/deploy/scripts/content-inventory.mjs`.
Expand Down
3 changes: 3 additions & 0 deletions plugins/stardust/skills/deploy/da-deploy-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ done
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
"https://admin.hlx.page/preview/$ORG/$REPO/$BRANCH/$P" # expect 200

# 3a'. ANY admin 4xx: the reason is in the `x-error` response header, not the body
# (deploy-batch appends it to lastError). `… N of 200 images` = the DA pipeline caps a
# document at 200 images (#125) → split long grids into /fragments/ documents.
# 3a. preview 409 "error from content-bus" — the error is OPAQUE (no per-asset
# detail); do NOT dead-end on it. Two cheap diagnostics, in order:
# (i) upload a known-good doc to the SAME path and re-preview — separates
Expand Down
5 changes: 4 additions & 1 deletion plugins/stardust/skills/deploy/scripts/deploy-batch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ async function call(method, url, { token, body } = {}, { retries = 4, backoffMs
try {
const res = await fetch(url, { method, headers: { Authorization: `Bearer ${token}` }, body });
status = res.status;
text = status >= 400 ? (await res.text()).slice(0, 200) : '';
// The admin API puts the REASON of a 4xx in the `x-error` header, not the body ("… N of 200 images":
// the DA pipeline caps a document at 200 images — long grids ride /fragments/ documents; #125).
const xErr = status >= 400 ? (res.headers.get('x-error') || '') : '';
text = status >= 400 ? `${(await res.text()).slice(0, 200)}${xErr ? ` [x-error: ${xErr.slice(0, 200)}]` : ''}` : '';
} catch (err) {
status = 0;
text = String(err.message || err);
Expand Down
31 changes: 28 additions & 3 deletions plugins/stardust/skills/diff/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ root carries the `placeholder` / `aria-label` / `title` values and the icons.
```bash
# 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.
# 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
Expand Down Expand Up @@ -110,6 +111,30 @@ measures the error page, and the flags (BLANK RENDER / content asymmetry) carry
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.
Expand Down
Loading
Loading