Skip to content

Development

claude edited this page Nov 10, 2026 · 1 revision

Development: How to read, run and change Preen's code.

Development

This page is for people who want to read, run or change Preen. It assumes you know JavaScript and the basics of browser extensions. Questions go to the issue tracker.

Version 0.0.29 sends no usage statistics and no debug submissions. The code for both is in packages/shared/core/telemetry.js and debug.js, but it has no server address and no host permission, so nothing leaves the computer. Releasing has the details.

What Preen is, technically

  • A Thunderbird MailExtension, manifest version 3. It needs Thunderbird 140 or newer, set by strict_min_version is 140.0 in apps/thunderbird/manifest.json.
  • Plain ES modules. No runtime dependencies. The build step only copies files: npm run build:thunderbird puts the add-on and the shared code into dist/thunderbird/, which is exactly what ships.
  • Tests use Node's built-in runner (node:test). There is no test framework to install.
  • The background script, background/main.js, runs as a module.

Repository layout

Path Purpose
apps/thunderbird/ The Thunderbird add-on: manifest, background/, mail/, storage/, ui/, icons/, and its CHANGELOG.md. core and providers here are links to the shared code.
apps/<other>/ Planned apps (betterbird, seamonkey, outlook, kmail, claws-mail, evolution, gmail, apple-mail). Each holds only a README.md for now.
packages/shared/ Code every app shares, with no mail-app calls: core/ and providers/.
tests/ Unit and UI tests (node:test). Also fixtures/ (sample emails and provider responses) and mocks/ (fake messenger, DOM and IndexedDB).
scripts/ build.sh builds an app into dist/<app>/. package.sh builds and zips the .xpi. dev.sh sets up a dev install.
docs/ Developer notes (jev-api-notes.md) and README images.
dist/ Build output. Ignored by git.
.github/workflows/ ci.yml and release.yml. See Releasing.
.github/labels.json The issue and pull request labels: one app:<name> per app, plus shared.

One repository holds every Preen app. Each app lives in apps/<name>/ and links in packages/shared/. Thunderbird is the first, and later apps are added the same way.

Architecture explains how the code is organised.

Setup

You need Node.js. The version is pinned in .nvmrc as 24. package.json has no dependencies, so there is nothing to npm install for the add-on or the tests.

node --version   # should match .nvmrc
npm test         # runs: node --test "tests/**/*.test.js"

Tests

npm test runs every file under tests/ that ends in .test.js.

  • tests/unit/ covers the pure logic in packages/shared/core/ and the background and mail code, using the mocks.
  • tests/ui/ loads the options, popup and activity pages against a fake DOM.
  • tests/providers/ checks the provider client and the host permissions.
  • tests/unit/package-script.test.js runs scripts/package.sh in a copy of the repo with a stub zip, and checks the key scan.
  • A test fails if a script uses innerHTML, outerHTML or insertAdjacentHTML.
  • tests/ui/page-guard.test.js checks that every page's preen-version meta tag matches the manifest version.

CI runs npm test on every push to main, on pull requests and on manual dispatch.

Linting

npx eslint apps packages tests                   # JavaScript (eslint.config.js, no plugins)
npx stylelint "apps/**/*.css"                    # CSS (.stylelintrc.json; stylelint is a devDependency)
npm run build:thunderbird
npx web-ext lint --source-dir dist/thunderbird   # add-on checks, on the build

web-ext lint is a Firefox linter, so it warns about Thunderbird-only permissions. Those warnings are expected. There is no lint script in package.json.

Building the .xpi

npm run build:thunderbird           # copies the add-on into dist/thunderbird/
npm run package                     # builds, then writes dist/preen-thunderbird-<version>.xpi
sh scripts/package.sh --downloads   # also copies it to ~/Downloads/preen-dev.xpi

What the script does:

  1. Reads the version from apps/thunderbird/manifest.json.
  2. Builds dist/thunderbird/: copies apps/thunderbird/ with the shared code as real files, leaving out CHANGELOG.md, .gitkeep and .DS_Store.
  3. Scans dist/thunderbird/ for anything that looks like an API key or private key, and stops if it finds one. It scans the build, not the .xpi, because zip compresses the files.
  4. Zips dist/thunderbird/ into dist/preen-thunderbird-<version>.xpi, with manifest.json at the top. It uses zip, or Python's zipfile when zip is not installed.

PREEN_DIST changes the output folder.

Trying it in Thunderbird

Quickest: run npm run build:thunderbird, then in Thunderbird open Tools, Developer Tools, Debug Add-ons, Load Temporary Add-on and pick dist/thunderbird/manifest.json. Rebuild and reload after each change.

For a full dev profile:

bash scripts/dev.sh

This one script, safe to run after every change:

  1. Installs Node.js if it is missing after asking first, and it needs sudo.
  2. Runs the unit tests.
  3. Builds the package.
  4. Creates a separate preen-dev Thunderbird profile on the first run only.
  5. Installs the add-on into that profile.
  6. Starts Thunderbird with that profile.

Your normal profile is never touched. Options: --skip-tests, --no-launch, --profile NAME, where the name must start with preen-. The script uses the Flatpak build of Thunderbird (org.mozilla.thunderbird).

Conventions in the code

  • Every source file starts with // SPDX-License-Identifier: PolyForm-Shield-1.0.0.
  • The comments say why, not what.
  • Pure logic goes in packages/shared/core/ and has no browser calls, so tests can run it directly and every app can use it. Anything that touches Thunderbird goes in apps/thunderbird/mail/, storage/ or background/.
  • Jev judges and code decides. The model returns probabilities. core/rules.js turns them into tags using the user's thresholds. When unsure, Preen does less, never more.
  • Add a setting in core/settings-schema.js, with its default and validation. Anything shown on the settings page has its help text in ui/options/options.html or ui/options/section-help.js.
  • The settings help is the documentation for most users, and this wiki is written from it, so keep the wording plain.

Where to start reading

  1. packages/shared/core/settings-schema.js holds every setting, its default and its validation.
  2. packages/shared/core/rules.js shows how scores become tags.
  3. apps/thunderbird/background/pipeline.js has the five-step flow for one message.
  4. packages/shared/providers/systemone.js makes the request to the provider.

Licence

PolyForm Shield License 1.0.0. See the LICENSE file. It is source-available, not open source. You may use the code for any purpose except to provide a product or service that competes with Preen.

Clone this wiki locally