# VibeGuard

VibeGuard is a local-first browser extension that hides toxic social-media posts. Content is classified on-device; post text is never sent to a classification service.

## Features

- Local toxicity classification using Transformers.js and a quantized ONNX model.
- Local CPU/WASM inference with ONNX Runtime.
- Configurable toxicity threshold and filtering mode: collapse posts with a Show button or hide them completely.
- Optional toxicity scores on filtered-post placeholders.
- Declarative site definitions with URL patterns, selectors, and stable identifiers.
- Built-in support for the official Mastodon web interface and Threads.
- Versioned JSON definition collections with weekly subscriptions and manual refresh.
- Toolbar popup with current-site status, matched definition, pause/resume control, and settings access.
- Firefox Manifest V2 and Chromium Manifest V3 builds.

The content script is loaded broadly so it can support navigation and dynamically rendered pages, but it does not inspect page content until a validated site definition matches the current page. Site definitions are data, not executable user code.

## Development

```sh
npm install
npm run typecheck
npm test
npm run build:firefox
npm run build:chromium
```

Each build writes a browser-specific package under `dist/<mode>/`. The packaged Apache 2.0 model is committed through Git LFS. Use the pinned-revision workflow below when updating it:

```sh
python3 -m venv .model-venv
. .model-venv/bin/activate
python3 -m pip install -r tools/requirements-model.txt

# Inspect architecture, labels, tokenizer, and resolved source revision.
python3 tools/convert_model.py inspect \
  --model wagesj45/toxic-comment-classifier

# Use the immutable commit printed by inspection for a release artifact.
python3 tools/convert_model.py prepare \
  --model wagesj45/toxic-comment-classifier \
  --revision <resolved-huggingface-commit>
python3 tools/convert_model.py validate
```

Preparation writes the Transformers.js-compatible files and `model-manifest.json` under `public/models/toxicity/`. The manifest records the source revision, Apache 2.0 license, toxic/non-toxic label indices, maximum sequence length, and int8 quantization format. Source PyTorch/safetensors weights are never copied into the extension.

If the model uses generic labels such as `LABEL_0` and `LABEL_1`, pass `--toxic-index` and `--non-toxic-index` to `prepare`; the command refuses to guess an ambiguous mapping.

VibeGuard injects a lightweight content script on ordinary web pages, but reads and processes content only when a validated site definition matches the page. The extension includes a bundled definition collection for the official Mastodon web interface and Threads. The Threads definition covers both `threads.com` and legacy `threads.net` URLs using semantic and data-attribute selectors. The default subscription follows the repository's raw JSON collection and refreshes weekly, while the bundled copy remains available offline. Additional JSON subscriptions, local overrides, and disabled definitions are managed in the options page. Definitions are declarative selectors and URL patterns, never executable code.

## Runtime architecture

Firefox uses a persistent MV2 background page and inference worker. Chromium uses an MV3 service worker as a router, an offscreen document, and a worker-backed classifier. Both builds share one bounded, prioritized inference queue and in-memory text-result cache.

The high-level processing path is:

```text
Supported page
    -> validated site definition
    -> generic content discovery
    -> shared inference queue
    -> local classifier
    -> threshold and filtering mode
```

The model is loaded once by the shared inference backend for each browser context. Content scripts send normalized text to the background runtime and apply the returned score to the matching DOM element. Results are cached in memory to avoid repeating work when dynamic sites recreate elements.

Inference runs locally through ONNX Runtime's WASM backend using the bundled quantized model.

## Options page

Open the extension’s options page to change the threshold, filtering mode, and score display. The Site definitions section manages JSON subscriptions, which refresh automatically once per week and can also be refreshed manually. Local overrides can be imported from or exported to JSON and are validated before they are saved. A failed or invalid subscription update leaves the last-known-good definitions active.

The options page uses the locally bundled [Bulma CSS](https://bulma.io/) v1.0.3 stylesheet. No options-page styling or runtime dependency is loaded from a CDN.

Click the toolbar icon to open the VibeGuard popup. It reports whether the current page is supported, names the matched site definition, and provides a persistent pause/resume control for that definition. Pausing immediately stops filtering in matching open tabs and removes VibeGuard’s current placeholders; resuming restarts discovery without requiring a page reload.

## Browser permissions

VibeGuard requests the following permissions:

- `storage` to save settings and definition configuration.
- `tabs` to associate inference requests with browser tabs.
- `<all_urls>` host access so supported sites can be detected and filtered.
- `offscreen` in the Chromium Manifest V3 build to host the long-lived inference document.

## Model and third-party licenses

The packaged toxicity model is distributed under the Apache License 2.0. Its source revision, label mapping, sequence length, and quantization details are recorded in `public/models/toxicity/model-manifest.json`.

VibeGuard also bundles [Bulma CSS v1.0.3](https://github.com/jgthms/bulma), which is distributed under the MIT License. The project’s own code is licensed under the terms in [LICENSE](LICENSE).

## Repository layout

| Path | Purpose |
| --- | --- |
| `src/content/` | Generic site discovery and DOM filtering. |
| `src/background/` | Firefox and Chromium background runtimes. |
| `src/inference/` | Worker, classifier, queue, and model metadata. |
| `src/options/` | Options-page behavior and VibeGuard-specific styling. |
| `src/shared/` | Settings, site-definition validation, types, and shared utilities. |
| `public/` | Browser manifests, options HTML, bundled CSS, and model files. |
| `tests/` | Unit tests for queues, filtering, definitions, hashing, and model metadata. |

## Testing and packaging

Run the type checker and test suite before building a package:

```sh
npm run typecheck
npm test
```

The bundled definition collection is generated from `src/content/definitions/collection.json` and the per-site files in `src/content/definitions/sites/`. Regenerate it after changing a site definition, then commit the resulting `default.json`:

```sh
npm run definitions:generate
npm run definitions:check
```

Build the browser-specific packages with:

```sh
npm run build:firefox
npm run build:chromium
```

The generated directories are `dist/firefox/` and `dist/chromium/`. Each contains the browser manifest, bundled JavaScript, the local `bulma.css` asset, and the model files required by that build. Load the generated directory as a temporary/unpacked extension during development.

To build versioned install packages for both browsers, run the shell packaging script from any directory:

```sh
./build.sh
```

The script runs both browser builds and writes `release/vibeguard-firefox-<version>.zip` and `release/vibeguard-chromium-<version>.zip`. The archives contain the generated extension files at their root and are ignored by Git.

### Logo and icons

Place the source logo at `public/logo.png`. The options page displays the generated 64 px version, and `build.sh` uses ImageMagick to generate the browser icon sizes `16`, `32`, `48`, `64`, `96`, and `128` under `icons/` in each release package. Install ImageMagick before running the packaging script; it accepts either the `magick` or `convert` command.
