Skip to content

tai2/star-hater

Repository files navigation

StarHater

A browser extension that hides star counts, like counts, and other bias-inducing engagement signals on the web. Ships with presets for GitHub, GitLab, X, and Twitter, and lets users add their own CSS-selector rules for any site they grant permission to.

  • Framework: WXT (Manifest V3)
  • UI: React 18 + TypeScript
  • Targets: Chromium (primary) and Firefox
  • License: MIT

Requirements

  • Node 22 (a .mise.toml is provided; mise install will pick it up)
  • pnpm 9.15.0 (enabled via corepack; the .mise.toml hook runs corepack enable on directory entry)

If you do not use mise, just make sure node -v reports 22.x and pnpm 9.15.0 is on your PATH.

Install

pnpm install

postinstall runs wxt prepare, which generates the .wxt/ types the TypeScript project depends on. If you ever see missing types for the auto-imported storage, browser, defineBackground, etc., re-run pnpm install or pnpm wxt prepare.


Development

Start a dev build with hot reload. WXT launches a fresh browser profile with the extension loaded.

pnpm dev            # Chromium
pnpm dev:firefox    # Firefox

Production build

pnpm build          # .output/chrome-mv3
pnpm build:firefox  # .output/firefox-mv2

pnpm zip            # zipped artifact for the Chrome Web Store
pnpm zip:firefox    # zipped artifact for AMO

Type-check

pnpm compile        # tsc --noEmit, strict

Architecture

entrypoints/
  background.ts       MV3 service worker: seeds storage, watches rule
                      changes, (un)registers dynamic content scripts for
                      user-defined hosts.
  content.ts          Static content script matched on the preset hosts.
                      Runs the rule engine at document_start.
  popup/              React popup UI: toggles presets, adds/removes user
                      rules, requests host permissions at rule-add time.

src/rules/
  presets.ts          Built-in rules for github.com, gitlab.com, x.com,
                      twitter.com.
  store.ts            Storage layer over wxt's `storage`. Persists user
                      rules and per-preset overrides; computes effective
                      rules.
  engine.ts           Injects a single <style> element with
                      `visibility: hidden !important` for selectors whose
                      url pattern matches the current page. Re-applies on
                      SPA URL changes (pushState / replaceState / popstate
                      / hashchange).
  dynamic-scripts.ts  Calls `chrome.scripting.registerContentScripts` for
                      user rule hosts that aren't already covered by the
                      static preset matches.
  messaging-bg.ts     Background-side message helpers.
  match.ts            Thin wrapper around WXT's `MatchPattern`.

wxt.config.ts         Manifest builder. `host_permissions` are limited to
                      the preset hosts in production; setting `E2E=1`
                      widens them to `*://*/*` (see "Testing" below).

A few intentional choices worth knowing about:

  • Hiding, not removing. The engine sets visibility: hidden !important rather than display: none or DOM removal. This avoids layout shifts and keeps the page's reflow behaviour intact.
  • Single injected <style> per page. The engine collects all active selectors into one rule list and re-uses one <style id="starhater-style"> element. Cleanup happens on the content script's AbortSignal.
  • Two sources of rules. Presets live in code (presets.ts); their on/off state is stored as an override map in sync:presetOverrides. User rules live entirely in sync:userRules. Both stores are watched by the background and the content script.
  • Permissions are requested, not preset. Adding a user rule calls browser.permissions.request({ origins: [urlPattern] }). Without that grant, syncDynamicContentScripts will silently skip registering the script for that host.

Testing

The Playwright suite under e2e/ boots a real Chromium with the unpacked MV3 build loaded as an extension and exercises:

  • The bundled GitHub preset against the real https://github.com/tai2/star-hater page.
  • Toggling presets from the popup.
  • The full lifecycle of a user-defined rule (add via popup → dynamic content script registered → element hidden → delete → element visible again) against a hermetic 127.0.0.1 server.

Run the suite:

pnpm e2e             # builds with E2E=1, then runs Playwright
pnpm e2e:build       # build only (writes .output/chrome-mv3)

The pnpm e2e script always runs e2e:build first — the production manifest is never tested. Two things to keep in mind:

  1. E2E=1 widens host_permissions to *://*/*. Without this, the popup's chrome.permissions.request(...) call triggers a native browser prompt that Playwright cannot dismiss. With it, the grant is implicit. Don't ship an E2E build.
  2. headless: false is required. Chrome extensions don't load in the default headless mode. The fixtures launch a chromium.launchPersistentContext with --load-extension + --disable-extensions-except. On CI we wrap the run in xvfb-run (see .github/workflows/ci.yml).

The HTML report lands in playwright-report/; failure traces go to test-results/. Both are gitignored.


Continuous integration

.github/workflows/ci.yml runs on every push to main, every pull request, and on manual dispatch. It has two jobs:

  • Type-checkpnpm compile.
  • Playwright E2Exvfb-run -a pnpm e2e on ubuntu-latest, with the Playwright browser cache keyed off pnpm-lock.yaml. The HTML report is uploaded as an artifact on every run; traces are uploaded on failure.

forbidOnly and retries: 2 only apply when CI=true is set.


Adding a new preset

  1. Append a Rule entry to PRESETS in src/rules/presets.ts. Use the preset.<host>.<what> id convention.
  2. If the host isn't already covered, add it to the matches array in entrypoints/content.ts and to PRESET_HOSTS in wxt.config.ts.
  3. Add a test under e2e/ exercising the preset on a real page.

For a user-supplied rule you don't need to touch any of this — adding it through the popup is enough.


Project layout reference

entrypoints/          Extension entry points (background, content, popup)
src/rules/            Rule model, storage, engine, dynamic scripts
e2e/                  Playwright tests + fixtures + local http helper
public/icon/          Extension icons (copied verbatim by WXT)
wxt.config.ts         Manifest configuration
playwright.config.ts  Playwright configuration
.github/workflows/    CI

About

Hide star counts, like counts, and other bias-inducing engagement signals on the web.

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors