Open-source project
zen-browser/desktop avatar
zen-browser/desktop

zen-desktop: a Firefox browser whose version string is a CI argument, not a manifest field

GitHub describes it as Welcome to a calmer internet. The repository metadata lists JavaScript as its primary language. The metadata lists the MPL-2.0 license. This article stays within the project description and details documented in the GitHub repository README.

44,662 stars1,788 forksJavaScriptMPL-2.0

At a glance

What is it?
The Zen Browser repository looks like a JavaScript project and is not one. Its build runs a Rust binary, two Python scripts and Firefox's own mach, its manifest says version 1.0.0 while the releases are numbered 1.22.3b, and its commit hook is declared with an empty command.
Who is it for?
Zen Browser fits a Firefox user who wants the browser's own configuration model back and who can wait for a channel to track an upstream release. It is a poor fit as a codebase to hack on without budget for a bootstrapped Firefox tree, and a poor fit for anyone who needs a versioned artifact to pin, because the version is assembled at build time.
Can I use it commercially?
Yes, with conditions. MPL-2.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Every release ends in b, and the manifest says 1.0.0

The release list reads 1.22.3b, published 2026-09-23, 1.22.2b from 2026-09-16 and 1.22.1b from 2026-09-12, each titled Release build with the version and a date in the name. The build date inside the name is a day earlier than the publication date on the first two, which is what you would expect from a build that is produced and then uploaded.

The manifest does not match. `package.json` declares `"version": "1.0.0"`, an empty `description` string, and a name of `zen-desktop`. None of that describes a build you can install.

The reason is visible in the CI script, which is `surfer ci --brand release --display-version`. The version is an argument supplied at build time rather than read from the manifest, which is why a manifest version of 1.0.0 and a shipped 1.22.3b can coexist without anybody noticing a bug. The consequence for a reader is concrete: the version you report in a bug is the one the browser shows you, and the number in the repository tells you nothing about which Firefox or which Zen patch you are running.

Release tracks Firefox 157.0 and Twilight tracks the release candidate

The Firefox Versions section gives two download links and two different upstream versions. Release is currently built using Firefox version `157.0`, and Twilight, at the same download page with a `?twilight` parameter, is currently built using Firefox version `RC 157.0`.

So the project runs two channels pinned to two different upstream states, a released Firefox and its release candidate. The sync scripts back that up: `sync` runs `python3 scripts/update_ff.py`, `sync:rc` adds `--rc`, and `sync:l10n` adds `--just-l10n` for a localisation only update.

The consequence for a user is that a bug can exist in one channel and not the other, and reproducing a report on the wrong channel wastes everyone's time. The consequence for a reader is subtler: two Zen builds a week apart may differ because Firefox changed underneath rather than because Zen did, so a regression bisected across releases can land in upstream code. Reporting the channel and the Firefox version together is the only way to make a report actionable.

One npm script drives Python, Rust and Firefox's own mach

The package is `type: module` with a `build` script of `surfer build`, which makes it look like an ordinary Node project until you read the rest. The dev start is `cd engine && python3 ./mach run --noprofile`. Linting is `cd engine && ./mach lint zen`. The C++ side has its own target, `test:gtest`, which is `cd engine && ./mach gtest Zen*`. And preferences are applied by a Rust binary: `ffprefs` is `cd tools/ffprefs && cargo run --bin ffprefs -- ../../`.

The tree confirms the three toolchains: a `.python-version`, a `.rust-toolchain` and a `.nvmrc` next to a `babel.config.json`, an `eslint.config.mjs` and a `tsconfig.json`, with `requirements.txt`, `surfer.json` and a `.husky/` directory.

The consequence is that two of the paths the scripts enter, `engine` and `tools/ffprefs`, are not in the top level listing, so they arrive from the bootstrap step rather than from a clone. A contributor therefore cannot run the lint or test scripts until the Firefox tree has been downloaded, which makes the first hour of setup a build problem rather than a coding one.

npm run init fetches Firefox, then rewrites preferences, then fetches service dumps

The bootstrap is one script with a chain behind it. `init` is `npm run download && npm run import && npm run bootstrap`, where `download` is `surfer download` and `bootstrap` is `surfer bootstrap`. The middle step is the interesting one, because `import` is itself three commands: `npm run ffprefs && npm run import:dumps && surfer import`.

So the order is: fetch an upstream Firefox tree, run the Rust preferences binary over the project, then run `import:dumps`, which is `python3 scripts/update_service_dumps.py`, and only then hand the result to `surfer import`.

The consequence is that a fresh setup makes a network call for service dumps before anything can be built, and `requests` is one of the pinned packages in `requirements.txt` for exactly that step. A failure there looks like a network error rather than a build error, and it happens before the toolchain has been exercised at all, which is a confusing first failure for anyone setting this up on a locked down network.

The lint-staged hook is declared with an empty command

The manifest contains `"lint-staged": { "**/*": "" }`. The key matches every file and the value is an empty string, so nothing runs at commit time.

The real gates are the npm scripts: `npm run lint`, which is `./mach lint zen` inside the engine directory, `npm run test`, which is `python3 scripts/run_tests.py`, and `npm run test:dbg`, which adds `--jsdebugger --debug-on-failure` for the run where a test hangs. There is a Rust-aware test target as well, and two license commands, `lc` for `surfer license-check` and `lc:fix` for the same with `--fix`.

The consequence is that a commit which breaks lint lands in the branch without complaint, and the first person to find out is whoever runs the suite, which as established needs the bootstrapped tree. For a project that inherits thousands of upstream files, the cheap local check that would catch the obvious mistakes is the one that is switched off, and the expensive one is the only one wired up.

requirements.txt pins seven packages exactly, and none of them is the product

The Python dependencies are short and fully pinned: `click==8.1.8`, `mypy-extensions==1.0.0`, `packaging==24.2`, `pathspec==0.12.1`, `platformdirs==4.3.6`, `pycodestyle==2.12.1` and `requests==2.34.2`.

Read as a set they say what the Python layer is for. `click` is a command line interface, `platformdirs` locates per platform directories, `pathspec` matches path patterns, `packaging` handles versions, `pycodestyle` is a linter, `mypy-extensions` supports typing, and `requests` fetches things over HTTP. None of them is a browser, a web framework or a rendering library, because the browser is Firefox and the Python is the harness around it.

The consequence is two things. A reader expecting a JavaScript browser codebase should not go looking for one here, since the JavaScript in this project is tooling and configuration. And because the pins are exact rather than ranges, a fresh environment either resolves to those versions or fails, which makes the interpreter itself a compatibility question, not just the packages.

A Firefox fork keeps .well-known/, .formal-git/ and a license check

Some entries in the top level only make sense if you remember that this is a fork. There is a `.well-known/` directory, a `.formal-git/` directory, a CODEOWNERS file, and a `use-moz-src` script that is `cd engine && ./mach use-moz-src`, which points the build at Mozilla's own source. The `lc` and `lc:fix` scripts run a license check over the tree, and `svgo.config.js` sits beside `babel.config.json` and `eslint.config.mjs` for asset optimisation.

Translation runs the same way it does in most forks: `crowdin.yml` with a `locales/` directory, coordinated through Crowdin rather than through pull requests. Bug reports and feature requests are split, with bugs on the GitHub Issues page and feature requests on GitHub Discussions, and the contribution guidelines are a file in the tree at `docs/contribute.md`.

The consequence is that the branding and licensing are a working surface rather than a formality. A fork of Firefox inherits upstream's distribution requirements, which is what a well-known directory and a license check are for, and it means a change to branding is not purely a preference. For anyone filing their first issue, the routing is the practical part: bugs go one way, proposals go another, and translations go to Crowdin.

Editorial conclusion

Zen Browser fits a Firefox user who wants the browser's own configuration model back and who can wait for a channel to track an upstream release. It is a poor fit as a codebase to hack on without budget for a bootstrapped Firefox tree, and a poor fit for anyone who needs a versioned artifact to pin, because the version is assembled at build time. Before you file anything, quote the display version from the browser rather than the repository manifest, decide whether Release or Twilight matches your Firefox, and read the contribution guidelines at docs/contribute.md, because the issue tracker and Discussions are the only two routes the project names.

Frequently asked questions

What is the definition of a desktop?

In this repository the word is part of the project name rather than a definition to look up. The package is `zen-desktop` and the project is Zen Browser, which the README describes as a firefox-based browser with the aim of pushing your productivity to a new level, licensed MPL-2.0.

Is a desktop a PC?

The repository does not discuss desktop computers. It is the source for Zen Browser, an MPL-2.0 licensed Firefox based browser whose newest listed build is 1.22.3b, published 2026-09-23 and built using Firefox version 157.0, with a Twilight channel built on RC 157.0.

how to install desktop

The repository does not document installing a desktop operating system or a desktop environment. For Zen itself the README links the download page at zen-browser.app/download, plus a Twilight channel at the same page with a `?twilight` parameter, and a separate documentation site at docs.zen-browser.app.

How do I open a desktop site?

The repository does not cover request desktop site behaviour, since it is the source tree for a browser rather than a guide to browser settings. It points to documentation at docs.zen-browser.app, GitHub Issues for bugs, GitHub Discussions for feature requests, and the contribution guidelines in docs/contribute.md.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/zen-browser-desktop.svg)](https://hysenlabs.com/projects/zen-browser-desktop)