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.
- package/CLONE-ALOYOGA-HEADER.md +76 -0
- package/CLONE-ANY-HEADER.md +106 -0
- package/CLONE-ANY-SITE.md +120 -0
- package/DEVELOP.md +144 -0
- package/LAUNCH-PROMPT.md +66 -0
- package/LEARNINGS.md +398 -0
- package/LICENSE +21 -0
- package/PLAYBOOK.md +260 -0
- package/README.md +219 -0
- package/WORKFLOW.md +257 -0
- package/bin/ppk +4 -0
- package/harness/adopt.js +52 -0
- package/harness/agent-setup.js +55 -0
- package/harness/behavior-selftest.js +243 -0
- package/harness/benchmarks/README.md +34 -0
- package/harness/benchmarks/battery.js +86 -0
- package/harness/benchmarks/detection-power.js +75 -0
- package/harness/capture-build-selftest.js +134 -0
- package/harness/capture-build.js +323 -0
- package/harness/doctor-selftest.js +58 -0
- package/harness/doctor.js +85 -0
- package/harness/fixtures/01-backdrop-color.js +51 -0
- package/harness/fixtures/02-line-strut.js +53 -0
- package/harness/fixtures/03-compat-mode.js +44 -0
- package/harness/fixtures/README.md +31 -0
- package/harness/human-qa-selftest.js +201 -0
- package/harness/human-qa.js +406 -0
- package/harness/merge-snapshot-selftest.js +56 -0
- package/harness/new-target.js +101 -0
- package/harness/regression.js +50 -0
- package/harness/score.js +58 -0
- package/harness/serve.js +149 -0
- package/harness/setup-selftest.js +93 -0
- package/harness/setup.js +158 -0
- package/harness/tunnel-selftest.js +76 -0
- package/harness/tunnel.js +241 -0
- package/harness/workflow-selftest.js +211 -0
- package/harness/workflow.js +739 -0
- package/package.json +40 -0
- package/skill/ditto-finish/SKILL.md +52 -0
- package/skill/pixel-perfect-clone/SKILL.md +49 -0
- package/tools/RUNBOOK.md +228 -0
- package/tools/behavior-capture.js +307 -0
- package/tools/behavior-worksheet.js +82 -0
- package/tools/browser-capture.js +240 -0
- package/tools/cli-selftest.js +136 -0
- package/tools/extract-fonts.js +133 -0
- package/tools/extract-icons.js +255 -0
- package/tools/merge-snapshot.js +56 -0
- package/tools/pixel-diff.js +845 -0
- package/tools/reassemble.js +63 -0
- package/tools/selftest.js +67 -0
- 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.
|
package/LAUNCH-PROMPT.md
ADDED
|
@@ -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.
|