This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is a collection of userscripts for browser extensions like Tampermonkey and Violentmonkey.
scripts/- Contains.user.jsuserscript files- Each script is standalone with a metadata header block
- Bump
@versionon every script change: Any edit to a.user.js(behavior, CSS, or metadata) must increment the@versionfield in its metadata header before committing. Never commit a script change without a version bump.
Each .user.js file begins with a metadata block:
// ==UserScript==
// @name Script Name
// @namespace Namespace
// @version X.Y
// @description Description
// @author Author
// @match https://*/*
// @exclude *.example.com/*
// @grant GM_addStyle
// ==/UserScript==Key metadata fields:
@match- URL patterns where the script runs@exclude- URL patterns to exclude@grant- GM API permissions (e.g., GM_addStyle for CSS injection)@version- Script version number
- Testing: Install scripts directly in Tampermonkey or Violentmonkey browser extension
- No build step: Scripts run directly without compilation
- Version management: Update
@versionin the metadata header when making changes
Scripts use GM_addStyle() to inject CSS styles into matched web pages for customization.
Since these scripts run against live third-party sites, verify CSS/behavior changes with a
local Playwright harness instead of relying only on manual browser installs. Always run Python
through uv — never pip install or bare python/python3. uv run --with <pkg> provisions
dependencies in an ephemeral environment, so nothing is installed globally. Pattern used for
the font script:
- Extract the injected CSS. If the script passes a plain template literal to
GM_addStyle(), grab it directly. If it builds the CSS with JS (variables/interpolation, e.g. anICON_EXCLUDE/MONO_SELECTORreused across rules), don't regex the literal — run the script's JS with a stub to capture the real output:node -e 'const fs=require("fs"); global.GM_addStyle=c=>fs.writeFileSync("/tmp/font.css",c); eval(fs.readFileSync("scripts/<name>.user.js","utf8"))'(the// ==UserScript==metadata is just comments, soevalignores it). - Build a static HTML fixture that reproduces the relevant markup. Crucially, include a
<style>block that mimics how real sites declare their own rules (e.g. icon fonts on.octicon/.material-icons/.fa, some with!important), loaded before the injected style so cascade/specificity behaves like production. Replicate the exact shape that breaks — e.g. a class-less<span data-cds="Icon" style="font-family: ...">nested inside a container whose class contains a mono keyword (editor/source/etc.), which is how a real icon got clobbered by the mono block's<sel> *descendant selectors. - Run Playwright via
uv—uv run --with playwright python <script>.py(first run also needs the browser binary:uv run --with playwright python -m playwright install chromium). The script injects the CSS into a<style id="injected">element, then asserts ongetComputedStyle(el).fontFamilyfor representative elements. - Assert both directions: the targeted elements get the new behavior and elements that
must be left alone (icon fonts, code/mono blocks) keep their original values. Specificity
regressions are easy to miss otherwise — e.g. a
:not()chain inflating specificity and clobbering a later rule, fixed by wrapping exclusions in:where()(zero specificity). When excluding icons, exclude them from every rule with!important(both the universal sans rule and the mono block) — an author!importantrule beats an icon's non-important inlinefont-family, so missing one rule still breaks the icon. - Clean up the temp fixture/script when done.
Always bump @version and re-run the harness after edits.
Playwright drives Firefox (Gecko) as well as Chromium, so run the same fixture through both
engines — p.chromium.launch() and p.firefox.launch() — and assert identical results.
Install Firefox once with uv run --with playwright python -m playwright install firefox
(Playwright ships its own unbranded Firefox matching Stable; the branded Firefox won't work).
This catches engine-specific CSS-parsing/specificity differences (e.g. confirming
:where(:not(...)) and case-insensitive [style*="..." i] behave the same in Gecko).
What a static fixture can't reproduce is per-engine markup: sites serve different DOM to
different browsers. Claude.ai renders icons as inline <svg> in Chrome (immune to
font-family) but as an icon font (Anthropicons-Variable on <span data-cds="Icon">) in
Firefox — so a font-clobbering bug was invisible in Chromium yet broke every icon in Firefox.
For that class of issue, also sanity-check the real site in Firefox. A no-DOM-access way to
triage from the page's own console: select candidate elements, toggle the injected <style>
(or adoptedStyleSheets) off, and compare getComputedStyle(el).fontFamily before/after to
see what the site intended vs. what we forced. Note Violentmonkey/Tampermonkey may inject
GM_addStyle via constructable document.adoptedStyleSheets rather than a <style> element,
so scan both.
The current scripts are single-file GM_addStyle() CSS injectors with no JS logic, so they
ship as-is with no build step. If a script ever grows real DOM/JS logic and needs modern
syntax (TypeScript, ES modules, JSX/components, CSS Modules), Violentmonkey supports a build
toolchain instead of hand-writing a single .user.js:
- Scaffold with their Yeoman generator:
npx -p github:violentmonkey/generator-userscript -p yo yo @violentmonkey/userscript - It sets up Babel + Rollup, with optional TypeScript, CSS Modules, UnoCSS, and SolidJS (remove the plugins you don't want — they're all optional).
- Source lives in
src/(meta.jsfor the metadata block,index.tsentry point);npm run devwatches andnpm run buildcompiles to a singledist/index.user.jsfor install. - Version/author auto-sync from
package.json. - Guide: https://violentmonkey.github.io/guide/using-modern-syntax/
Only adopt this when a script's complexity justifies it; keep simple CSS-injection scripts build-less.
- Violentmonkey API: https://violentmonkey.github.io/api/gm/