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
11 changes: 3 additions & 8 deletions .claude/hooks/post-edit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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".
11 changes: 0 additions & 11 deletions .claude/hooks/stop-gate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
Expand Down
21 changes: 1 addition & 20 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:*)",
Expand All @@ -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
}
]
}
Expand Down Expand Up @@ -98,18 +91,6 @@
}
]
}
],
"SessionEnd": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node \"${CLAUDE_PROJECT_DIR:-.}/scripts/watch-daemon.mjs\" status",
"timeout": 15
}
]
}
]
}
}
5 changes: 0 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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`.
Expand Down
Loading
Loading