diff --git a/.claude/hooks/post-edit.mjs b/.claude/hooks/post-edit.mjs index 29558ac..335ff1e 100755 --- a/.claude/hooks/post-edit.mjs +++ b/.claude/hooks/post-edit.mjs @@ -65,11 +65,6 @@ if (problems.length) { process.exit(2); } -// The file is good, so make sure it is actually VISIBLE somewhere. `ensure` is -// idempotent and the already-running path is a pid check plus one loopback -// request, so this costs nothing on the common path. -try { - console.log(run('node', ['scripts/watch-daemon.mjs', 'ensure']).trim()); -} catch { - /* the live loop is a convenience; never fail an edit over it */ -} +// There is no live-injection loop to revive here. A change reaches the browser +// only through a real Violentmonkey install, which is the operator's action — +// see CONTRIBUTING.md "Testing a change". diff --git a/.claude/hooks/stop-gate.mjs b/.claude/hooks/stop-gate.mjs index b8c3ae4..97b4765 100755 --- a/.claude/hooks/stop-gate.mjs +++ b/.claude/hooks/stop-gate.mjs @@ -25,17 +25,6 @@ try { const run = (cmd, args) => execFileSync(cmd, args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }); -// Revive the live loop on EVERY turn, not only the turns that touched a -// userscript — "stays running" has to survive a crash during a docs change too. -// Idempotent, and the already-running path is a pid check plus one loopback -// request. Never allowed to block a stop. -try { - const out = run('node', ['scripts/watch-daemon.mjs', 'ensure']).trim(); - if (out && !/already running/.test(out)) console.log(out); -} catch { - /* the browser is a convenience; a clean stop is not */ -} - let changed = []; try { changed = run('git', ['status', '--porcelain', '--', '*.user.js']) diff --git a/.claude/settings.json b/.claude/settings.json index a7f1605..bc777ab 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -43,9 +43,7 @@ "Bash(typos:*)", "Bash(markdownlint-cli2:*)", "Agent(Explore)", - "Agent(Plan)", - "Bash(node scripts/watch-daemon.mjs:*)", - "Bash(pkill -f chrome-for-testing:*)" + "Agent(Plan)" ], "deny": [ "Bash(npm install:*)", @@ -66,11 +64,6 @@ "type": "command", "command": "node \"${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/session-start.mjs\"", "timeout": 20 - }, - { - "type": "command", - "command": "node \"${CLAUDE_PROJECT_DIR:-.}/scripts/watch-daemon.mjs\" ensure", - "timeout": 30 } ] } @@ -98,18 +91,6 @@ } ] } - ], - "SessionEnd": [ - { - "matcher": "*", - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PROJECT_DIR:-.}/scripts/watch-daemon.mjs\" status", - "timeout": 15 - } - ] - } ] } } diff --git a/.gitignore b/.gitignore index 6cb80e4..3cc5663 100644 --- a/.gitignore +++ b/.gitignore @@ -15,11 +15,6 @@ result result-* .direnv/ -# The browser `nix run .#watch` drives: a PROJECT-LOCAL Chrome profile plus its -# log. Hundreds of megabytes, and it holds cookies for whatever you were testing -# — it must never be committed, and it is not shared with your real browser. -.nix-browser/ - # Editor and OS noise .DS_Store Thumbs.db diff --git a/CHANGELOG.md b/CHANGELOG.md index ecd6b88..abd3fab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,24 @@ Entries are grouped by script. Within a release, use the ## [Unreleased] +### Removed + +- **The entire Chrome for Testing / DevTools-Protocol toolchain**, because it was + not the browser this project is developed against and therefore not + verification. Gone: `nix/chrome-for-testing.nix`, `nix/violentmonkey.nix`, + `scripts/cdp.mjs`, `scripts/userscript-watch.mjs`, + `scripts/userscript-verify.mjs`, `scripts/watch-daemon.mjs`, the `browser` / + `watch` / `verify` / `watch-status` / `watch-stop` / `browser-bump` flake + commands, the two Claude Code hooks that kept the loop alive, and the 470 MB + `.nix-browser/` profile. The repository went from 474 MB to **3.8 MB** on disk. +- **The `allowUnfreePredicate` with it.** It existed to admit exactly one + package, Chrome for Testing; nothing unfree is left in the flake, so a + contributor no longer needs any unfree allowance to get a dev shell. +- **Five ESLint globals** — `fetch`, `WebSocket`, `AbortController`, `setTimeout`, + `clearTimeout` — which only the CDP client needed. `scripts/` is now + `meta-lint.mjs` alone, and the globals list is back to what a file there + actually references, so `no-undef` keeps meaning something. + ### Added - Repository scaffolding: contribution guide, code of conduct, security policy, @@ -163,6 +181,36 @@ Entries are grouped by script. Within a release, use the ### Changed +- **The documented way to test a change is to install it.** The browser is + ungoogled-chromium **152.0.7977.64** with **Violentmonkey 2.48.0** from the + Chrome Web Store, and the only channel into a page is the manager's own + install path. `CONTRIBUTING.md` gains a **Testing a change** section; + `CLAUDE.md`'s two browser sections were rewritten around it; `README.md` no + longer offers `nix run .#watch` as the one-command way to try something. +- **Why, stated as the trade it was.** The CDP lane ran a different browser at a + different version against a throwaway profile, so whatever it proved was not + what a reader gets. It was measured drifting: a feature was reported "verified + on all five hosts" against Chrome for Testing **154** on a machine whose actual + browser is **152**, and two of those five hosts turned out to be scroll-locked + in ways the rig papered over. thumbwall's own `WHY` block had recorded its + original measurements on `Chromium 152.0.7977.64` all along — the real browser. +- **Measuring a selector is now the script's own job.** `selector-verify.mjs` + returned a `GENERATED` verdict mechanically; with no tool, `CONTRIBUTING.md` § + Measure the live page carries the rule instead — anchor on ARIA roles, `href` + values and data attributes, never a generated class and never `aria-label` or + link text, both of which are localised. Log the count from the script behind a + debug flag that ships off, and break the selector on purpose to prove the + failure mode. +- **Two findings kept as page facts, not rig facts.** xhamster ships + `body.xh-scroll-disabled` (`overflow-y: hidden`) on a clean profile with no + dialog behind it, and pornhub holds `scrollHeight === innerHeight` behind its + age modal. Both measured 2026-09-29 identical with a script armed and with it + torn down, so neither is the script's doing — check the page scrolls at all + before blaming a scroll-driven feature. +- The per-host marked-node counts in `CLAUDE.md`'s script map (106 / 153 / 108 / + 1439) are labelled as CDP-era figures: orders of magnitude, not numbers to diff + against. + - CI's Node is 20 → **22**, matching `nodejs_22` in `flake.nix`, so the npm path and the flake path run ESLint on the same runtime. Node 20 went end-of-life in April 2026 and eslint 10 wants `^20.19 || ^22.13 || >=24`. diff --git a/CLAUDE.md b/CLAUDE.md index 030f7e7..01f1381 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -102,9 +102,6 @@ fix # eslint --fix + markdownlint --fix bump