Prove the probe can still see — the check that checks the checker
qa/probes.test.mjsThe failure it prevents
Every one of these checks reports by NOT finding something. So the day someone renames a component and the probe's selector matches nothing, the audit reports a clean page — forever, on every route, in every CI run. That is the worst way for a check to break, because it looks like good news. There is no alert, no red build, no gradual decay you would notice: one commit and the gate is a decoration that still prints green.
The prompt
You are going to make this project's audit prove, on every run, that its probes can still see. This is not a documentation task and it is not "add some tests" — it is a specific structure, and if you produce anything less than fixtures plus imports of the real probe functions, you have produced nothing.
**Step 1 — extract the probes.** Every function that gets serialized into the browser (anything passed to `page.evaluate` or `page.evaluateOnNewDocument`) moves out of the audit script into `qa/probes.mjs` as a **named export**. They may use browser globals only: no imports, no closures over module scope, no TypeScript that needs compiling — they cross a process boundary as source text. Put a file-header comment saying exactly that, because the first person to add a helper import will otherwise get a baffling `ReferenceError` inside a headless Chrome.
**Step 2 — write `qa/probes.test.mjs` with `node:test`, importing those exact exports.** Not copies. Not re-implementations. If the test file contains its own version of the probe logic, delete it and import — a copy proves the copy works.
```js
import { INSTALL_SHIFT_OBSERVER, OVERFLOW_CULPRITS, MEASURE_TEXT_EDGES } from "./probes.mjs";
```
**Step 3 — serve fixtures from an in-process HTTP server.** `node:http` `createServer`, `listen(0)` for a free port, a plain object mapping path → tiny HTML string. No dev server, no framework, no build. The whole suite must run in under fifteen seconds or it will be excluded from the fast gate, which defeats the point.
**Fixtures must render under the same reset the app renders under.** If the app ships a CSS reset that sets `box-sizing: border-box` globally, put that in the fixture — otherwise padding lands outside the box you are asserting on and the fixture quietly stops describing the thing being tested. Write that as a comment in the fixture, not just in your head.
**Step 4 — every probe gets three tests. All three, no exceptions.**
- **True positive** — a page that breaks the rule. The probe must report it AND name the element. Assert both; a probe that finds a violation but names nobody produces a finding no one can act on.
- **True negative** — a page that obeys the rule. The probe must report exactly zero. A noisy probe gets muted by the team, and a muted probe is a deleted probe.
- **Hard negative** — a page that *looks* like a violation and is legitimate. A marquee wider than the viewport but clipped by its own parent. A grid cell that starts at a different x than the body copy. The probe must stay silent. This is the test that keeps the audit trusted, and it is the one people skip.
**Plus, for any probe that mutates the page** (anything that lifts a clip, toggles a style, scrolls): a restoration test that reads the mutated property back afterwards and asserts the original value.
**Assertion messages carry the consequence, not the expectation.** Not `assert.ok(cls > 0)`. Write: `"the observer reported no shift on a page that deliberately grows a box — every CLS 0 in the audit is meaningless until this passes"`. The person reading that message in six months is debugging at 11pm and needs to know what the failure means, not what the number was.
**Step 5 — wire it into the fast gate**, alongside typecheck and lint, and **specifically not** behind the same flag as the slow browser audit. The self-test must run on every commit even when the full rendered pass is skipped, because a broken probe reports a clean page whether or not you ran the browser audit that day.
**Step 6 — the rot drill. Do this, and paste the real output; it is the only evidence the suite is load-bearing.**
1. Copy `qa/` to a scratch directory outside the repo and symlink `node_modules` into it. **Never run this drill in the real tree.**
2. In the *copy*, rename one selector or attribute the probe depends on to something that matches nothing — exactly what a component rename does silently.
3. Run the test suite in the copy.
4. It must fail, with a message that names what was expected and what was measured (`expected 2 elements, measured 0`).
5. Delete the scratch directory. Paste the failing output into your hand-back.
If the suite still passes after the drill, the tests are decorative: they are asserting on the probe's *shape* rather than its *findings*. Go back and add the assertion that would have caught it, then run the drill again.
**Hand back:** `qa/probes.mjs`, `qa/probes.test.mjs`, the passing run (`node --test qa/probes.test.mjs`), the failing rot-drill output, and one sentence per probe stating what its hard negative is. A probe with no hard negative is a probe you have not thought about yet.What it produces
qa/probes.mjs (browser-side functions as named exports) and qa/probes.test.mjs (node:test + an in-process fixture server) covering every probe with a true positive, a true negative, a hard negative, and a restoration test where applicable — wired into the fast gate.
How you prove it works
The rot drill itself, which the user runs and watches fail: copy qa/ to a scratch dir, rename one attribute the probe queries, run the tests, see not ok — expected 2 elements, measured 0. Before that, the fixtures each prove one direction: a box that grows 100→400px 800ms after load (probe must fire), an identical page that never grows (probe must report exactly 0), an element 200px past the viewport under body{overflow-x:hidden} (probe must fire through the clip), and a 3000px marquee clipped by its own parent (probe must NOT fire).