pixel-perfect-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CLONE-ALOYOGA-HEADER.md +76 -0
  2. package/CLONE-ANY-HEADER.md +106 -0
  3. package/CLONE-ANY-SITE.md +120 -0
  4. package/DEVELOP.md +144 -0
  5. package/LAUNCH-PROMPT.md +66 -0
  6. package/LEARNINGS.md +398 -0
  7. package/LICENSE +21 -0
  8. package/PLAYBOOK.md +260 -0
  9. package/README.md +219 -0
  10. package/WORKFLOW.md +257 -0
  11. package/bin/ppk +4 -0
  12. package/harness/adopt.js +52 -0
  13. package/harness/agent-setup.js +55 -0
  14. package/harness/behavior-selftest.js +243 -0
  15. package/harness/benchmarks/README.md +34 -0
  16. package/harness/benchmarks/battery.js +86 -0
  17. package/harness/benchmarks/detection-power.js +75 -0
  18. package/harness/capture-build-selftest.js +134 -0
  19. package/harness/capture-build.js +323 -0
  20. package/harness/doctor-selftest.js +58 -0
  21. package/harness/doctor.js +85 -0
  22. package/harness/fixtures/01-backdrop-color.js +51 -0
  23. package/harness/fixtures/02-line-strut.js +53 -0
  24. package/harness/fixtures/03-compat-mode.js +44 -0
  25. package/harness/fixtures/README.md +31 -0
  26. package/harness/human-qa-selftest.js +201 -0
  27. package/harness/human-qa.js +406 -0
  28. package/harness/merge-snapshot-selftest.js +56 -0
  29. package/harness/new-target.js +101 -0
  30. package/harness/regression.js +50 -0
  31. package/harness/score.js +58 -0
  32. package/harness/serve.js +149 -0
  33. package/harness/setup-selftest.js +93 -0
  34. package/harness/setup.js +158 -0
  35. package/harness/tunnel-selftest.js +76 -0
  36. package/harness/tunnel.js +241 -0
  37. package/harness/workflow-selftest.js +211 -0
  38. package/harness/workflow.js +739 -0
  39. package/package.json +40 -0
  40. package/skill/ditto-finish/SKILL.md +52 -0
  41. package/skill/pixel-perfect-clone/SKILL.md +49 -0
  42. package/tools/RUNBOOK.md +228 -0
  43. package/tools/behavior-capture.js +307 -0
  44. package/tools/behavior-worksheet.js +82 -0
  45. package/tools/browser-capture.js +240 -0
  46. package/tools/cli-selftest.js +136 -0
  47. package/tools/extract-fonts.js +133 -0
  48. package/tools/extract-icons.js +255 -0
  49. package/tools/merge-snapshot.js +56 -0
  50. package/tools/pixel-diff.js +845 -0
  51. package/tools/reassemble.js +63 -0
  52. package/tools/selftest.js +67 -0
  53. package/tools/sink.js +80 -0
@@ -0,0 +1,76 @@
1
+ # Prompt — clone the aloyoga.com header, pixel-perfect
2
+
3
+ Hand this to an agent that has this kit's `tools/` available and a browser it can
4
+ drive (or DevTools access). It is self-contained.
5
+
6
+ ---
7
+
8
+ Build a **pixel-perfect clone of the header at https://www.aloyoga.com/**, measured
9
+ at **1728px** viewport width, as a component in my project (`components/Header.tsx`;
10
+ create it from scratch). This is a study/clone build — header **typography and
11
+ iconography** are the target.
12
+
13
+ **Before writing any code, read these and follow them exactly:**
14
+ `PLAYBOOK.md`, `tools/RUNBOOK.md`, `README.md`, and `LEARNINGS.md`. The rules there
15
+ override any assumption you have.
16
+
17
+ **Method — measure the LIVE site, match what it actually renders:**
18
+
19
+ 1. **Discover the fonts — don't assume any family.** First read the live DOM to find
20
+ what the header actually uses: on aloyoga.com's DevTools console inspect the
21
+ computed `font-family` of the header text elements (e.g.
22
+ `getComputedStyle(document.querySelector('header a')).fontFamily`) and list the
23
+ `@font-face` families the page loads. Then extract exactly those:
24
+ `extractFonts(/<family-you-found>/i)` (or `extractFonts()` for all, then keep the
25
+ header ones). Do not hard-code a font name from memory — the family is whatever the
26
+ live site reports.
27
+
28
+ 2. **Extract the rest of the real assets (never redraw or retype).** Run
29
+ `extractIcons('header')`; capture the logo/wordmark `<svg>` and any header rasters
30
+ by their real URLs. Self-host the woff2s; use the icons' **exact** captured
31
+ data-URIs. If a header element renders a `font-weight` **heavier than any real face
32
+ the site shipped** (a synthesized/faux-bold), reproduce that synthesis over the
33
+ real max-weight face — do not substitute a different/heavier font.
34
+
35
+ 3. **Measure the live DOM, not a spec doc.** Capture every header element's complete
36
+ box at 1728px: geometry; the **text-glyph box via `Range`** (not the element box);
37
+ the **painted glyph** for icons/logo (SVG bbox, or the background element **plus
38
+ its `background-position`** — never the clickable wrapper); the full box-model; and
39
+ the font **including `line-height`, `letter-spacing`, `color`, and `underline`**.
40
+ The visible marks don't co-center — measure where each one actually paints.
41
+
42
+ 4. **Build to those measurements**, then verify — never from a screenshot.
43
+
44
+ **Verification is the gate (this is what "done" means):**
45
+
46
+ - Capture both pages at the **same 1728px width** (see `tools/RUNBOOK.md` for the fast
47
+ sequence: clone → `pxSend`; live CSP → inject source directly + `pxStash`/`pxRead`,
48
+ never `fetch`+`eval`).
49
+ - Loop:
50
+ ```sh
51
+ node tools/pixel-diff.js live.json clone.json --visual
52
+ ```
53
+ Fix each ❌ and re-run **until it exits 0**. Then run strict
54
+ (`node tools/pixel-diff.js live.json clone.json`) and **fix or explicitly document**
55
+ every structural delta (Ground rule 4) — a colour / `underline` / `visibility` row
56
+ is never "structural."
57
+ - **Close coverage:** auto-enumerate every painted element in the header band (own
58
+ text, background-image, or `<svg>`) and add a `pxTargets` entry for each one that
59
+ has none; re-diff. Exclude off-screen (x<0), `display:none`, and flyout panels below
60
+ the bar. The coverage list must end **empty** — a green table only proves the
61
+ elements you measured.
62
+
63
+ **If a human (or you) later spots something off** — a wrong colour, a missing
64
+ underline, a shifted glyph — do **not** guess the property. Run the human-flagged
65
+ loop from `PLAYBOOK.md` Phase 6:
66
+ ```sh
67
+ # pxInspect({text:"..."}) on live + clone → el_live.json / el_clone.json
68
+ node tools/pixel-diff.js --inspect el_live.json el_clone.json
69
+ ```
70
+ The **PAINT** bucket is the exact fix list; apply those values and re-run until the
71
+ paint bucket is empty (exit 0).
72
+
73
+ **Definition of done — paste the proof:** `--visual` exits 0 with every header
74
+ element covered, the strict run's structural deltas are each fixed or documented, and
75
+ you paste the final passing diff output (real measured numbers, not prose). Do not
76
+ claim "pixel-perfect" from anything but a diff that exits 0.
@@ -0,0 +1,106 @@
1
+ # Prompt template — clone any site's header, pixel-perfect
2
+
3
+ Reusable template. Fill in the three placeholders below (or tell the agent their
4
+ values), then hand the file to an agent that has this kit's `tools/` available and a
5
+ browser it can drive (or DevTools access). It is otherwise self-contained.
6
+
7
+ **Fill in:**
8
+ - `{{URL}}` — the live page to clone, e.g. `https://www.example.com/`
9
+ - `{{WIDTH}}` — the measurement viewport width in px, e.g. `1728`
10
+ - `{{OUTPUT_PATH}}` — where to create the component, e.g. `components/Header.tsx`
11
+
12
+ ---
13
+
14
+ Build a **pixel-perfect clone of the header at {{URL}}**, measured at **{{WIDTH}}px**
15
+ viewport width, as `{{OUTPUT_PATH}}` (create it from scratch). This is a study/clone
16
+ build — header **typography and iconography** are the target.
17
+
18
+ > **Pick the build strategy first (LEARNINGS #19).** If `{{OUTPUT_PATH}}` is a standalone
19
+ > clone page (e.g. `targets/<name>/clone/index.html`), **build by capture** — capture the
20
+ > settled live DOM (`pxSendDom`) and run `ppk capture-build <name>`; it inherits live's
21
+ > doctype and drawing techniques by construction, then verify with the gates below as
22
+ > usual. The measured-rebuild method that follows is for when the output is a **component
23
+ > in your own stack** (like a `.tsx` file) that capture can't express.
24
+
25
+ **Before writing any code, read these and follow them exactly:**
26
+ `PLAYBOOK.md`, `tools/RUNBOOK.md`, `README.md`, and `LEARNINGS.md`. The rules there
27
+ override any assumption you have.
28
+
29
+ **Method — measure the LIVE site, match what it actually renders:**
30
+
31
+ 1. **Discover the fonts — don't assume any family.** First read the live DOM to find
32
+ what the header actually uses: on {{URL}}'s DevTools console inspect the computed
33
+ `font-family` of the header text elements (e.g.
34
+ `getComputedStyle(document.querySelector('header a')).fontFamily`) and list the
35
+ `@font-face` families the page loads. Then extract exactly those:
36
+ `extractFonts(/<family-you-found>/i)` (or `extractFonts()` for all, then keep the
37
+ header ones). Do not hard-code a font name from memory — the family is whatever the
38
+ live site reports.
39
+
40
+ 2. **Extract the rest of the real assets (never redraw or retype).** Run
41
+ `extractIcons('header')`; capture the logo/wordmark `<svg>` and any header rasters
42
+ by their real URLs. Self-host the woff2s; use the icons' **exact** captured
43
+ data-URIs. If a header element renders a `font-weight` **heavier than any real face
44
+ the site shipped** (a synthesized/faux-bold), reproduce that synthesis over the
45
+ real max-weight face — do not substitute a different/heavier font.
46
+
47
+ 3. **Measure the live DOM, not a spec doc.** Capture every header element's complete
48
+ box at {{WIDTH}}px: geometry; the **text-glyph box via `Range`** (not the element
49
+ box); the **painted glyph** for icons/logo (SVG bbox, or the background element
50
+ **plus its `background-position`** — never the clickable wrapper); the full
51
+ box-model; and the font **including `line-height`, `letter-spacing`, `color`,
52
+ `-webkit-font-smoothing`, and `underline`**. The visible marks don't co-center —
53
+ measure where each one paints. The diff engine already captures the two marks that
54
+ used to slip a green sweep — **`font.smoothing`** (`-webkit-font-smoothing`:
55
+ `antialiased` vs `auto` changes perceived weight while `font-weight` matches) and the
56
+ **underline as a box** (`underline.{thickness,x,width,top,bottom}`, taken off whichever
57
+ element draws it — often an **ancestor** `border-bottom`, not the text) — and
58
+ `--visual` compares both; trust the diff on them. Set your clone root to
59
+ `antialiased`/`grayscale`. What you still own: **reproduce the drawing technique**
60
+ (redraw the underline as that same `border-bottom` on a same-sized box so its Y
61
+ *emerges* from the box model — never a hand-tuned offset, which rasterises unlike a
62
+ real border and fails a flicker compare even at ~0.01px), and apply the same
63
+ box-not-boolean principle by hand to any mark the tool doesn't special-case
64
+ (`box-shadow`, `outline`).
65
+
66
+ 4. **Build to those measurements**, then verify — never from a screenshot.
67
+
68
+ **Verification is the gate (this is what "done" means):**
69
+
70
+ - Capture both pages at the **same {{WIDTH}}px width** (see `tools/RUNBOOK.md` for the
71
+ fast sequence: clone → `pxSend`; live CSP → inject source directly + `pxStash`/`pxRead`,
72
+ never `fetch`+`eval`).
73
+ - Loop:
74
+ ```sh
75
+ node tools/pixel-diff.js live.json clone.json --visual
76
+ ```
77
+ Fix each ❌ and re-run **until it exits 0**. Then run strict
78
+ (`node tools/pixel-diff.js live.json clone.json`) and **fix or explicitly document**
79
+ every structural delta (Ground rule 4) — a colour / `underline` / `visibility` row
80
+ is never "structural."
81
+ - **Close coverage:** auto-enumerate every painted element in the header band (own
82
+ text, background-image, or `<svg>`) and add a `pxTargets` entry for each one that
83
+ has none; re-diff. Exclude off-screen (x<0), `display:none`, and flyout panels below
84
+ the bar. The coverage list must end **empty** — a green table only proves the
85
+ elements you measured.
86
+
87
+ **If a human (or you) later spots something off** — a wrong colour, a missing
88
+ underline, a shifted glyph — do **not** guess the property. Run the human-flagged
89
+ loop from `PLAYBOOK.md` Phase 6:
90
+ ```sh
91
+ # pxInspect({text:"..."}) on live + clone → el_live.json / el_clone.json
92
+ node tools/pixel-diff.js --inspect el_live.json el_clone.json
93
+ ```
94
+ The **PAINT** bucket is the exact fix list; apply those values and re-run until the
95
+ paint bucket is empty (exit 0). Two rules that turn a multi-round flag into one fix:
96
+ **resolve the element that actually *paints* the mark** (if the paint bucket is empty
97
+ but it still looks wrong, you inspected the text when an **ancestor** draws it — inspect
98
+ the ancestor's box: `top`/`height`/`box-sizing`/`border-bottom`), and **fix the whole
99
+ mark by reproducing its box model + technique in one shot** (a decoration's thickness,
100
+ width, offset, and drawing element together) rather than nudging the one facet named.
101
+ Then flicker/overlay the two at 1:1 — a technique mismatch survives a green number.
102
+
103
+ **Definition of done — paste the proof:** `--visual` exits 0 with every header
104
+ element covered, the strict run's structural deltas are each fixed or documented, and
105
+ you paste the final passing diff output (real measured numbers, not prose). Do not
106
+ claim "pixel-perfect" from anything but a diff that exits 0.
@@ -0,0 +1,120 @@
1
+ # Prompt template — clone a whole site (or page), one shot, human-approved
2
+
3
+ The ONE-SHOT pipeline: hand this to an agent and wait; the deliverable comes back
4
+ pixel-perfect **with receipts** — every gate green, in order, and a real human's
5
+ approval recorded as the `human` phase. The operator's only job is to have run
6
+ `npx cpyany setup` once (PingHumans auth) and have `cloudflared` installed.
7
+
8
+ **Fill in:**
9
+ - `{{URL}}` — the live page to clone, e.g. `https://www.example.com/`
10
+ - `{{WIDTH}}` — the measurement viewport width in px, e.g. `1512`
11
+ - `{{NAME}}` — the target name, e.g. `example`
12
+
13
+ ---
14
+
15
+ Produce a **pixel-perfect, human-approved clone of the page at {{URL}}** at
16
+ **{{WIDTH}}px**, as the standalone target `targets/{{NAME}}/clone/`. Work
17
+ unattended until `ppk gate {{NAME}} done` exits 0 — that is the definition of
18
+ done, and it now includes a PingHumans human approval. Do not stop at "the
19
+ numbers match": stop when the receipts say a human agreed.
20
+
21
+ **Before writing any code, read and follow exactly:** `PLAYBOOK.md`,
22
+ `tools/RUNBOOK.md`, `LEARNINGS.md`, `WORKFLOW.md`. Their rules override any
23
+ assumption you have.
24
+
25
+ **Work the FAST loop (RUNBOOK "The fast fix loop") — the reviewer answers in
26
+ minutes, so the agent is the bottleneck, not the human:**
27
+ - Start `node tools/sink.js` and `node harness/tunnel.js --sink` (background)
28
+ FIRST — every capture (pxSend/pxSendDom/pxBehaviorSend) then delivers in one
29
+ call through the sink tunnel; stash/chunked-read is a last resort.
30
+ - After a scoped fix, re-capture only the affected targets and fold them in with
31
+ `node tools/merge-snapshot.js` — full N-target re-captures are for the final
32
+ pass only (the done gate enforces one clean full capture at the end).
33
+ - When a fix's acceptability is uncertain (contested content, blur quality,
34
+ "is this what they meant?"), spend a ~$0.05 micro-poll —
35
+ `ppk human {{NAME}} poll "…" --choices "Yes,No"` — BEFORE burning a full test
36
+ round on it. Polls advise; only full rounds satisfy the human gate.
37
+ - **On every REFILE, pass `--changelog "what changed since the last review"`** —
38
+ a reviewer who isn't told what changed reviews blind ("did you fix
39
+ anything?" is a wasted round). And **never escalate a reviewer's comment
40
+ out of the loop**: respond with a fix or an in-round explanation and
41
+ refile — the verdict is the only decision channel.
42
+ - **ALL human contact goes through `ppk human {{NAME}} …` — NEVER call the
43
+ PingHumans/cpyany MCP tools directly.** Direct calls are invisible to the
44
+ workflow (no recorded round → the human gate can't verify the answer) and
45
+ skip the kit's template. And know the two shapes: **a poll may reference ONE
46
+ side only** (a live-observation or taste question — "on the real page, does
47
+ X scrub?"); any question naming BOTH the clone and the live page is a
48
+ COMPARISON and must be a **filed test** (`ppk human {{NAME}} file
49
+ [--region "…"]`) so the reviewer gets the side-by-side + align view +
50
+ pinned comments. The poll command refuses comparison-shaped questions.
51
+ - Before every `human file`: self-QA the dynamics (overlay/flicker-compare the
52
+ clone vs live at 2–3 rotation/reveal states) — a defect you catch yourself
53
+ costs seconds; one the reviewer catches costs a round.
54
+
55
+ **The sequence (each step's gate must pass before the next — `ppk status {{NAME}}`
56
+ always tells you what's next):**
57
+
58
+ 1. **Scaffold + pin the target.** `ppk new {{NAME}} {{URL}} {{WIDTH}}` → advance
59
+ `target`. If your browser can't reach exactly {{WIDTH}}px, record the real
60
+ width in `target.json` and measure everything at that width.
61
+
62
+ 2. **Build by capture — never hand-rebuild** (LEARNINGS #19; PLAYBOOK Phase 4).
63
+ Settle the live page (load, scroll bottom-and-back, confirm stable
64
+ scrollHeight + node count twice), capture the doctype-exact DOM
65
+ (`pxSendDom('http://localhost:7799/dom.html')`, sink running in
66
+ `targets/{{NAME}}/`), then `ppk capture-build {{NAME}} --qa-toolbar`.
67
+ Delivery blocked? Fall back per RUNBOOK (stash/chunked `pxRead`; or curl the
68
+ SSR HTML **only after verifying** its structure matches the hydrated DOM —
69
+ element-count comparison minimum). Attest `assets` with real evidence.
70
+
71
+ 3. **Measure BOTH pages at the same width** (RUNBOOK). Full-page `pxTargets`:
72
+ every distinct painted element; for long repeats (cards, rows) sample
73
+ first + last + a spread (the 37signals precedent) and say so in NOTES.md.
74
+ Enumerate coverage with `element.checkVisibility({checkVisibilityCSS:true,
75
+ checkOpacity:true})` — per-node `getComputedStyle` lies inside closed
76
+ `<details>` (opendesign lesson).
77
+
78
+ 4. **Gate loop:** `--visual` until 0 fails → close `coverage` → `strict` (fix or
79
+ document every structural delta — a colour/underline row is NEVER structural).
80
+
81
+ 5. **Reproduce the dynamics (`behavior` phase — PLAYBOOK Phase 5c).** Inject
82
+ `tools/behavior-capture.js` on the live tab, name the marquees/hover triggers you
83
+ can see, and run the discovery pass (`pxBehaviorSend` → `behaviors-live.json`) —
84
+ it greps `@keyframes` + markers for candidates, then a MutationObserver +
85
+ scripted scroll sweep confirms and MEASURES what actually fires. Reproduce each
86
+ inventoried behavior in one vanilla `clone/fixes.js` (values from the
87
+ measurements, never eyeballed), rebuild with `ppk capture-build {{NAME}}
88
+ --fixes`, run the same discovery on the clone (`behaviors-clone.json`), and loop
89
+ until `ppk gate {{NAME}} behavior` passes. Genuinely irreproducible content
90
+ (WebGL/canvas generative) is documented in `behavior-deviations.json` — never
91
+ silently frozen. Re-run the pixel gates after: `done` re-verifies everything.
92
+
93
+ 6. **Human rounds — driven by you, judged by them.** `ppk serve {{NAME}}` →
94
+ `ppk tunnel {{NAME}}` (it refuses to record a tunnel that isn't serving the
95
+ clone byte-identically — trust that check) → `ppk human {{NAME}} file`
96
+ (scope-pinned template auto-generated from your coverage list; the draft url
97
+ defaults to the verified tunnel). Wake on results with
98
+ `npx cpyany wait <ping_id>` as a background task. On flags: run the
99
+ PLAYBOOK Phase 6 `--inspect` drill-down on the element that PAINTS the
100
+ flagged mark, fix the whole mark in one shot, re-run the gates, then
101
+ `ppk human {{NAME}} file` again — the refile loop is the product working,
102
+ not a failure. The gate passes only on an explicit approving verdict pick;
103
+ comments alone never pass, however positive.
104
+
105
+ 7. **Finish.** `ppk advance {{NAME}} human`, then `ppk advance {{NAME}} done` —
106
+ done re-runs every gate against the artifacts on disk. Paste the final
107
+ `ppk status {{NAME}}` table, the passing `--visual` output, the behavior gate's
108
+ verified-inventory line, and the approving round from
109
+ `targets/{{NAME}}/human-qa.json` in your report.
110
+
111
+ **Dynamics honesty:** the captured HTML/CSS stay byte-exact; `fixes.js` is the ONLY
112
+ script and only re-drives what discovery measured. Anything excused in
113
+ `behavior-deviations.json` must also be noted in NOTES.md — the human test template
114
+ marks behavior steps INFORMATIONAL as a backstop for taste/feel and documented
115
+ deviations, not as a substitute for the behavior gate.
116
+
117
+ **Keep `targets/{{NAME}}/NOTES.md` current** (iteration-log table: what was
118
+ flagged, whether the gate caught it, the fix, the kit-change candidate). A miss
119
+ the gates didn't catch is the most valuable output of the run — flag it loudly
120
+ in your final report (DEVELOP.md meta-loop).
package/DEVELOP.md ADDED
@@ -0,0 +1,144 @@
1
+ # DEVELOP — how to make the kit better by cloning real sites
2
+
3
+ This is the **meta-loop**: you clone real sites to *exercise* the kit, and every miss
4
+ you hit becomes a permanent improvement to the kit — a tool check, a regression fixture,
5
+ and a generalized instruction. The **kit** (`tools/` + the `.md` instructions) is the
6
+ product; **targets** (`targets/<name>/`) are disposable instances you build and throw
7
+ away. Site B inherits everything site A taught, because the lessons live in the shared
8
+ kit, not in a per-site checklist.
9
+
10
+ Two nested loops:
11
+
12
+ ```
13
+ OUTER (improve the kit) ── for each target site ──▶ INNER (clone it to green)
14
+ ▲ │
15
+ └──────── every MISS the gate didn't catch ─────────┘
16
+ becomes: tool check + fixture + generalized instruction
17
+ ```
18
+
19
+ ---
20
+
21
+ ## Inner loop — clone one site to green
22
+
23
+ Per target, this is just PLAYBOOK.md + RUNBOOK.md, made turn-key:
24
+
25
+ ```sh
26
+ node harness/new-target.js siteA https://www.example.com/ 1728 # scaffold targets/siteA/
27
+ node tools/sink.js & # snapshot receiver :7799
28
+ node harness/serve.js siteA # serve clone + /tools :8080
29
+ ```
30
+
31
+ Then, driven by measurements (never guesses):
32
+ 1. **Measure the live site** at the target width (RUNBOOK) → save `targets/siteA/live.json`.
33
+ 2. **Build** `targets/siteA/clone/` — **default: by capture** (LEARNINGS #19):
34
+ `pxSendDom('http://localhost:7799/dom.html')` on the live tab, then
35
+ `node harness/capture-build.js siteA` (self-hosted CSS/fonts, doctype preserved).
36
+ Hand-rebuild to measurements only when exercising the rebuild path is the point of
37
+ the run — that's where the technique-mismatch lessons came from.
38
+ 3. **Capture** the clone → `targets/siteA/clone.json`, then score:
39
+ ```sh
40
+ node harness/score.js siteA # visual PASS/FAIL, fix list, and Δ vs the last run
41
+ ```
42
+ 4. Fix each ❌, re-capture, re-score. `score.js` records every run to `scores.jsonl` and
43
+ prints whether you got **better** (visual fails ↓). Done = `--visual` green **and**
44
+ coverage empty (every painted leaf has a target) **and** strict deltas fixed-or-documented.
45
+
46
+ `score.js` turns "is this iteration better?" into a number instead of a vibe — that's the
47
+ inner-loop progress signal.
48
+
49
+ ---
50
+
51
+ ## Outer loop — the miss protocol (this is the engine)
52
+
53
+ The valuable event is a human (or your own eyes) flagging something **that a green
54
+ `--visual` did not catch**. Don't just patch that one clone — that's how you iterate once
55
+ per site forever. Convert the miss into a kit improvement so it's caught *everywhere, next
56
+ time, automatically*:
57
+
58
+ 1. **Localize the element that actually *paints* the mark** — it may be an ancestor.
59
+ `node tools/pixel-diff.js --inspect el_live.json el_clone.json`. If the paint bucket is
60
+ empty but it still looks wrong, you inspected the wrong element (PLAYBOOK Phase 6).
61
+ 2. **Make it a MEASURED failure.** Teach the capture to record it as a number/box
62
+ (`tools/browser-capture.js` **and** `tools/pixel-diff.js`, kept schema-identical) and
63
+ `--visual` to compare it. The test: would the sweep now fail on the defect? (This is
64
+ how `font.smoothing` and the underline **box** were added — LEARNINGS #12/#13.)
65
+ 3. **Lock it in with a fixture — and score the change.** Add `harness/fixtures/NN-name.js`
66
+ that fails *without* your tool change (see `harness/fixtures/README.md`); now it can
67
+ never silently return. Then add the defect **and any false-positive it risks** to
68
+ `harness/benchmarks/battery.js` and prove the change is a real improvement:
69
+ `node harness/benchmarks/detection-power.js --vs HEAD` — the bar is **+N defect classes
70
+ caught, 0 regressions** (0 missed defects, 0 new false positives). A change that
71
+ false-positives a control is *not* an improvement, even if it catches the new defect.
72
+ (The `bg` backdrop gate — #16 — was adopted exactly this way: the naive version
73
+ false-positived on translucent/whitespace controls; the battery caught it *before*
74
+ commit, and the gate was narrowed to opaque-only until the A/B came back clean.)
75
+ 4. **Re-score every existing target.** `node harness/score.js siteA` (and siteB, …) — a
76
+ tool change must not regress a clone that was already green. And
77
+ `node harness/regression.js` must stay green.
78
+ 5. **Generalize the instruction.** Write the durable lesson in `LEARNINGS.md`, tagged
79
+ 🔒 (gate-enforced — trust the diff) or 👁 (judgment — the diff can't see it). Compress
80
+ the how-to in `PLAYBOOK.md` / `RUNBOOK.md` to "the gate checks this; your job is the
81
+ technique." **Grow the tool, not a per-site checklist.**
82
+
83
+ If a miss genuinely *can't* be measured (a taste/technique call — e.g. "reproduce the
84
+ drawing technique so it rasterises identically"), it stays a 👁 judgment lesson. Name it
85
+ explicitly in LEARNINGS' "gate vs your eyes" list so it isn't forgotten — but prefer
86
+ pushing misses down into 🔒 whenever they can be measured.
87
+
88
+ ---
89
+
90
+ ## Cloning site B (and C, …)
91
+
92
+ Run the **same inner loop** on a new target. Because A's misses became tool checks, B
93
+ starts with a stronger gate — a wrong-thickness underline or a heavier smoothing fails on
94
+ B's first score without anyone re-noticing. B's *new* misses feed the same outer loop.
95
+ Over several sites the pattern converges: fewer human rounds per site, because the gate
96
+ keeps absorbing what used to need eyes.
97
+
98
+ Pick diverse targets on purpose — a site with `text-decoration` underlines, one with
99
+ `box-shadow` dividers, one with a sticky/scrolled header, one RTL — each stresses a
100
+ different corner and surfaces a different class of miss to absorb.
101
+
102
+ ---
103
+
104
+ ## What "better" means (the scoreboard)
105
+
106
+ - **Per target:** `scores.jsonl` — `visualFails` trending to 0, then coverage empty, then
107
+ strict deltas each fixed-or-documented. A later run with fewer visual fails is better.
108
+ - **Per kit:** `node harness/regression.js` stays green (no known class of miss can
109
+ recur), and the **number of human rounds to green trends down** across successive
110
+ targets. That downward trend *is* the kit getting better.
111
+ - **Per gate change:** `node harness/benchmarks/detection-power.js` scores the gate at
112
+ **all-defects-caught / 0-false-positives** (absolute mode, run by regression), and a
113
+ *proposed* change is judged by `--vs HEAD` — **+N caught, 0 regressions** or it's not
114
+ adopted. This turns "does this gate change help?" into a number, the same way `score.js`
115
+ does for a clone. See `harness/benchmarks/README.md`.
116
+ - **Guardrail:** a kit change that makes any previously-green target regress, or turns
117
+ regression red, is not an improvement — revert or fix.
118
+
119
+ ---
120
+
121
+ ## Layout
122
+
123
+ ```
124
+ pixel-perfect-kit/
125
+ ├── README.md PLAYBOOK.md LEARNINGS.md CLONE-ANY-HEADER.md ← the product: instructions
126
+ ├── DEVELOP.md ← you are here (the meta-loop)
127
+ ├── tools/ pixel-diff.js browser-capture.js extract-*.js sink.js
128
+ │ └── selftest.js guards the gate's guarantees (underline box + smoothing)
129
+ ├── harness/ the dev framework
130
+ │ ├── new-target.js scaffold targets/<name>/
131
+ │ ├── capture-build.js DEFAULT build: clone from the captured live DOM (LEARNINGS #19)
132
+ │ ├── human-qa.js the human phase: scope-pinned PingHumans rounds as a gate
133
+ │ ├── serve.js static server (clone + /tools)
134
+ │ ├── score.js score live-vs-clone, compare to last run
135
+ │ ├── regression.js selftest + every fixture + the detection battery
136
+ │ ├── fixtures/ one file per class-of-miss (can never recur)
137
+ │ └── benchmarks/ detection-power battery — score a gate change old-vs-new (--vs HEAD)
138
+ └── targets/ ← DISPOSABLE per-site instances (git-ignored); the kit is the product
139
+ └── <name>/ target.json clone/ live.json clone.json scores.jsonl NOTES.md
140
+ ```
141
+
142
+ Rule of thumb: if a change only helps one site, it belongs in `targets/<name>/`. If it
143
+ helps the *next* site, it belongs in the kit — as a tool check first, an instruction
144
+ second.
@@ -0,0 +1,66 @@
1
+ # Launch prompt — hand a fresh agent a ppk clone run
2
+
3
+ Fill the three values and give this to any agent with browser automation + shell access.
4
+ The kit's docs carry the method; this wrapper carries only what lives outside the repo
5
+ (the environment) and the run contract. Refined across the astryx + iphone17 runs.
6
+
7
+ ---
8
+
9
+ You are driving the pixel-perfect-kit (`ppk` on PATH; run `ppk where` to locate the
10
+ installed kit's directory — every doc referenced below lives there; run `ppk doctor`
11
+ first and surface any failure to the user before starting). Targets are created under
12
+ the CURRENT working directory (`targets/<name>/`).
13
+
14
+ Your task: follow the kit's one-shot template at <KIT>/CLONE-ANY-SITE.md with these values:
15
+ - {{URL}} = <the page to replicate>
16
+ - {{WIDTH}} = <fixed measurement viewport width, e.g. 1512>
17
+ - {{NAME}} = <target name, e.g. acme>
18
+
19
+ The template names the docs to read first (PLAYBOOK.md, tools/RUNBOOK.md, LEARNINGS.md,
20
+ WORKFLOW.md — their rules override any assumption you have) and defines done:
21
+ `node harness/workflow.js gate {{NAME}} done` exits 0 — which includes the `behavior`
22
+ phase (every JS-driven dynamic reproduced with measured values or excused with a
23
+ reviewer-readable reason) and the `human` phase (a real reviewer's approving verdict,
24
+ filed and verified through the kit's tooling). The reviewer answers within minutes —
25
+ keep the loop tight and act on every verdict immediately.
26
+
27
+ Environment notes (operational, not workflow):
28
+ - Use your browser-automation tooling (e.g. the claude-in-chrome MCP — load the core
29
+ set in ONE ToolSearch call). Fresh tabs; verify innerWidth/devicePixelRatio before
30
+ every capture; if the requested width is unreachable, record the actual width in
31
+ target.json and measure everything at that width.
32
+ - Long-running processes (sink, serve, tunnels, `npx cpyany wait`) run as background
33
+ Bash tasks. Sandboxed Bash may need the sandbox disabled for network commands.
34
+ - Delivery: start `node tools/sink.js` and `node harness/tunnel.js --sink` FIRST — the
35
+ automation extension blocks page→localhost fetch (a clean ~4s abort), and the sink
36
+ tunnel restores one-call delivery for every capture (pxSend / pxSendDom /
37
+ pxBehaviorSend). A `text/plain` form POST to the sink also works when the site's CSP
38
+ has no form-action directive. Stash + chunked pxRead is the LAST resort.
39
+ - Do the work directly — NO sub-agents (their results orphan when you stop). If a
40
+ session limit kills you, the receipts carry the state: your successor orients from
41
+ disk, so keep NOTES.md, FINDERS.md, and the ledgers current as you go.
42
+
43
+ Rules the kit ENFORCES — don't fight the refusals, they are the method:
44
+ - ALL human contact through `ppk human {{NAME}} …` (the MCP contact tools are
45
+ permission-denied). Polls may reference ONE side only; anything naming both clone and
46
+ live is a comparison and must be a filed test. Filing requires every pre-human gate
47
+ green. Every refile carries `--changelog "what changed since your last review"`.
48
+ - Never --force. Never invent: values come from measurements, mechanisms from the
49
+ site's own captured CSS/markup/config (a guessed attribute value is an invention; an
50
+ honest static frame beats fabricated motion). When a behavior's mechanism is
51
+ unknowable from artifacts, the worksheet prints the question to ask the human —
52
+ ask it before you build.
53
+ - Screenshots triage and smoke-check; they never certify a match. Numbers certify what
54
+ numbers reach; the reviewer certifies the rest — through the platform, never out of
55
+ band.
56
+
57
+ If {{NAME}} already exists (resuming): orient from disk before touching anything —
58
+ `ppk status {{NAME}}`, run the pre-human gates read-only, read NOTES.md fully (history,
59
+ protocols, environment findings), FINDERS.md (persisted selectors + decoys), and
60
+ human-qa.json (round history). Trust receipts over any prior agent's claims.
61
+
62
+ Your final message is a factual run report: each phase with its real gate output, the
63
+ behavior worksheet summary (observed/declared/how disposed), human round history
64
+ (ping ids, verdicts, what you fixed), kit friction — a flag the gates MISSED is the
65
+ most valuable output of the whole run — and the exact commands a reviewer runs to
66
+ re-verify. Raw facts over prose polish.