Building from source and contributing
Build Revue yourself, run its tests, use your own OAuth apps and translate it.
Revue is a Rust program built with GPUI. Every command for working on it is a Task task, and works the same on Linux, macOS and Windows.
What you need#
- Git, to get the source code.
- Rust, installed with rustup. The repository pins the exact version in
rust-toolchain.toml, and rustup fetches it on the first build. - Task (taskfile.dev), version 3.
- System libraries for GPUI, depending on your system (below).
Linux: system packages
On Debian or Ubuntu, these are the packages the project's own builds install:
sudo apt install pkg-config cmake clang libxkbcommon-dev libxkbcommon-x11-dev \
libwayland-dev libvulkan-dev libx11-xcb-dev libxcb1-dev libfontconfig-dev \
libfreetype-dev libasound2-dev libzstd-dev
On other distributions, install the same libraries under their names there: a C toolchain with clang and cmake, pkg-config, and the development files of xkbcommon (with its X11 part), Wayland, Vulkan, xcb, fontconfig, freetype, ALSA and zstd.
The checks (task check:ci) also need Python 3 with PyYAML (python3-yaml).
A built Revue links only libc, libxcb and libxkbcommon. Wayland, Vulkan and fontconfig are loaded when it runs, so the computer that runs Revue needs a working Vulkan driver.
macOS and Windows
- macOS: the Xcode Command Line Tools (
xcode-select --install). Revue compiles its Metal shaders when it runs, so the full Xcode isn't needed. - Windows: the Rust MSVC toolchain, which needs the Visual Studio Build Tools with the C++ workload and a Windows SDK. Revue's shaders come precompiled with the source, so no shader compiler is needed.
Get the code and run it#
-
Clone the repository:
git clone https://forgejo.cleangame.dev/thatrichan/revue.git cd revue -
Build and start Revue:
task dev
The first build takes a while; later ones are much faster.
A dev build is called Revue Dev and has a green icon, so you can tell it from an installed Revue. On Linux, task dev also adds a desktop entry for it, so your desktop doesn't group it with the installed Revue.
To open a pull request straight away, pass its address after --:
task dev -- https://github.com/owner/repo/pull/123
Advanced: when another build is running
If another cargo build (a second task dev, your editor's rust-analyzer) holds the build lock, task dev starts the last build instead of waiting, and says it may be out of date.
Try a release build#
task dev:release
This builds an optimized release and runs it with --immutable. A release build uses the installed Revue's settings, sign-ins and data. With --immutable it works on a temporary copy of them, deleted when you quit, and leaves the real ones untouched.
Advanced: what --immutable changes
- Settings, data and the credential store are copied; the copy of the credentials is kept in memory only.
- It doesn't install updates and doesn't register itself for
revue://links. - Sign-in tokens that a service renews are still saved for real, because the service stops accepting the old ones.
revue --immutabledoes the same with any build (see Command line and environment variables).
The main tasks#
task --list shows every task with a description. The ones you'll use most:
| Task | What it does |
|---|---|
task dev | Build and run a dev build ("Revue Dev"); arguments after -- go to Revue |
task dev:release | Build and run a release build on a copy of your data (--immutable) |
task check:ci | The checks: workflow YAML, rustfmt, clippy with warnings as errors, and the tests |
task server:dev | Run revue-server at http://localhost:7377 (see Self-hosting revue-server) |
task shoot -- … | Take screenshots of Revue in a headless session (Linux) |
task version | Print Revue's version |
Advanced: task check and task check:quick
task check runs the same checks as task check:ci plus the release build (slow: it's optimized with LTO). task check:quick leaves the release build out. Unlike check:ci, both build with the normal dev profile.
They work in two ways:
- With Nix installed, they run in the Nix dev shell (
nix develop), which brings the Rust toolchain, the system libraries and Python. Nothing else is needed. - Without Nix (or with
task check NIX=0), they use the tools on your PATH, on Linux, macOS and Windows: Rust from rustup (it installs the pinned toolchain with clippy and rustfmt), your system's libraries (see What you need) and Python 3 with PyYAML (pip install pyyaml, orpython3-yaml). Without Python and PyYAML, the workflow YAML check is skipped with a note; CI still runs it.
Advanced: other tasks
task iconsregenerates every icon format fromassets/revue-icon.png, including the green dev icons.task file-icons -- <downloads folder>vendors the file-type icon themes and regeneratessrc/file_icons_table.rs.task windows-shadersprecompiles GPUI's Windows shaders (withfxc.exeunder Wine). Only needed after updating the patched GPUI Windows crate.task package PLATFORM=linux|windows|macos BIN=<path>packages a built binary intotarget/release-dist.task buildandtask release:build:linux|windows|macosbuild the release files in Docker images (see Releasing).task agent-cargo -- …is cargo for AI coding sessions working in the repository at once: it runs one cargo at a time in a shared build folder.task mcp:forgejoandtask mcp:githubstart MCP servers for the repository, for coding assistants (from.mcp.json).
Advanced: task shoot
task shoot runs Revue in a headless sway session, drives it with keystrokes and saves screenshots, without touching your desktop or your Revue data. It needs sway, grim, wtype and wlrctl.
task shoot -- OUTDIR [--size WxH] [--light] [--clean] [--cache] [--settings JSON] [--demo] [--] [revue args] ::: STEP...
- It signs in with
REVUE_GITHUB_TOKEN, or else the gh CLI's token, and starts from an empty cache. It never reads your Revue settings, sign-ins or cache. --sizesets the window size (default1920x1200),--lightuses the light theme,--cleansigns in only with aREVUE_GITHUB_TOKENyou pass explicitly,--cachestarts from a copy of your HTTP cache (faster, but it holds your pull requests; not with--clean),--settingspasses settings as JSON, and--demoruns the demo on made-up data.- Each STEP is one of
shot:NAME,key:KEYS,press:KEY,combo:MOD+KEY(e.g.combo:ctrl+shift+f),sleep:SECONDS,type:TEXT,click:X,Y,hover:X,Yordrag:X1,Y1,X2,Y2. - It runs
target/release/revue, or the binary inREVUE_BIN.
Linux: installing a release
The Linux release archive comes with its own small Taskfile. After extracting it, task install installs Revue to ~/.local (the program, a desktop entry and icons), or system-wide with PREFIX=/usr/local task install as root.
Running the tests#
The tests are ordinary Rust tests:
cargo test # all of them
cargo test i18n # only the ones whose name contains "i18n"
Before you send a change, run task check:ci. It's what CI runs: formatting, clippy with warnings as errors, and all tests.
Advanced: the server's tests
revue-server is a separate program with its own Cargo.toml in server/. Run its tests there: cd server && cargo test. Building its Docker image runs them too.
The account pages run the app's encryption (crates/revue-vault) in the browser, compiled to WebAssembly from crates/revue-vault-wasm, a workspace of its own with its own Cargo.lock (a test in crates/revue-vault checks it locks the same versions as the app's). It isn't committed: the server's Docker image builds it, and task server:wasm builds it into server/assets/revue-vault.wasm (ignored by git) for task server:dev and task server:js-test, which run it first. That needs the wasm32-unknown-unknown target, which the task adds with rustup. Without the module the server still builds and passes its tests, but the account pages can't run (their wasm answers 503), and a release build with accounts stops with an error.
The .env file#
Local settings for development, such as your OAuth apps, go in a .env file in the checkout. Git ignores it.
-
Copy the template:
cp .env.example .env -
Fill in the values you have. Leave the others empty.
-
Run
task dev. It loads.envinto both the build and the app.
A service without its values just offers token sign-in. .env.example lists every variable, with a comment on each.
| Variable | For |
|---|---|
REVUE_GITHUB_CLIENT_ID, REVUE_GITHUB_CLIENT_SECRET | GitHub sign-in in the browser (the secret renews sign-ins made on the web account) |
REVUE_BITBUCKET_CLIENT_ID, REVUE_BITBUCKET_CLIENT_SECRET | Bitbucket sign-in in the browser |
REVUE_JIRA_CLIENT_ID, REVUE_JIRA_CLIENT_SECRET | Jira sign-in in the browser |
REVUE_FORGEJO_CLIENT_IDS | Forgejo, Codeberg and Gitea sign-in in the browser |
REVUE_SLACK_CLIENT_ID, REVUE_SLACK_CLIENT_SECRET, REVUE_SLACK_REDIRECT_URL | Connecting Slack |
REVUE_IMAGES_TOKEN | Uploading images in comments (see Self-hosting revue-server) |
Advanced: global.env
Project-wide settings that aren't secret live in global.env, which is checked in and compiled into every build: the server address (REVUE_SERVER_URL) and the crash-report address (REVUE_SENTRY_DSN). A variable of the same name set while building wins, and an empty one turns it off.
Only release builds send crash reports: the release builds set REVUE_RELEASE=1.
Using your own OAuth apps#
Revue can sign in to every service in the browser (OAuth), next to the forms for pasting a token. A service shows its Sign in with … button only when Revue knows an OAuth app for it.
Official releases come with Revue's apps built in. When you build Revue yourself, register your own app on each service you want, once, and give Revue its client ID (and secret, where there is one):
- when Revue runs, e.g.
REVUE_GITHUB_CLIENT_ID=… revue, or through.envwithtask dev; - when Revue is built: the build bakes in whatever these variables hold, so the binary carries them.
A value set when Revue runs wins over one built in. An empty value counts as unset.
| Service | How sign-in works | You need |
|---|---|---|
| GitHub | Device flow: you type a code on github.com | Client ID (and its secret, to renew sign-ins made on the web account) |
| Bitbucket Cloud | Browser comes back to http://localhost:47392/callback | Key and secret |
| Jira (Atlassian) | Browser comes back to http://localhost:47392/callback | Client ID and secret |
| Forgejo, Codeberg, Gitea | Browser comes back to http://127.0.0.1:47392/callback (with PKCE) | A client ID per server |
| Slack | Browser comes back to http://localhost:47392/callback (with PKCE, user scopes) | Client ID |
Advanced: token lifetimes and renewal
- Access tokens from Bitbucket (2 hours), Atlassian (1 hour) and Forgejo (1 hour) expire. Revue renews them with the refresh token 2 minutes before they run out, and saves the new pair on that computer only, never in the synced settings folder.
- GitHub OAuth apps registered since August 2026 expire their tokens after 8 hours by default, with a refresh token. Revue renews those the same way. An older app's tokens don't expire, unless you turn on Require token expiration in its settings.
- The browser has 10 minutes to come back to Revue.
GitHub#
- Go to https://github.com/settings/developers → OAuth Apps → New OAuth App. To own it as an organization, use the organization's Settings → Developer settings → OAuth Apps instead.
- Fill in:
- Application name:
Revue - Homepage URL: your project page
- Authorization callback URL:
http://127.0.0.1:47392/callback(GitHub requires one, but the device flow doesn't use it)
- Application name:
- Click Register application.
- Tick Enable Device Flow and save.
- Copy the Client ID (it starts with
Ov23li) and set it asREVUE_GITHUB_CLIENT_ID. - If your revue-server offers Sign in with GitHub on the web account with this app, click Generate a new client secret and set it as
REVUE_GITHUB_CLIENT_SECRETtoo. Signing in from the app doesn't need it, but GitHub renews a sign-in made on the web (its web flow) only with the secret, so without it those sign-ins stop working after 8 hours.
Advanced: GitHub scopes and organizations
- Revue asks for the scopes
repo read:org, the same as a classic token. - Organizations with OAuth app access restrictions show Revue as "requested". An organization owner has to approve it (organization Settings → Third-party access → OAuth application policy) before its private repositories appear.
Bitbucket Cloud#
- In a workspace you administer, open Workspace settings → Apps and features → OAuth consumers → Add consumer (
https://bitbucket.org/<workspace>/workspace/settings/api). - Fill in:
- Name:
Revue - Callback URL:
http://localhost:47392/callback, exactly - Leave This is a private consumer unticked.
- Permissions: Account: Read, Workspace membership: Read, Repositories: Read, Pull requests: Write (includes read)
- Name:
- Click Save.
- Expand the consumer to see its Key and Secret.
- Set
REVUE_BITBUCKET_CLIENT_IDto the Key andREVUE_BITBUCKET_CLIENT_SECRETto the Secret.
Any Bitbucket user can then authorize the consumer for their own account, not only members of your workspace.
Advanced: browsing code with a Bitbucket OAuth sign-in
Bitbucket accepts OAuth tokens only on api.bitbucket.org, not for the bitbucket.org source downloads that the Code tab and Find in Files use. When that download is refused, Revue fetches the commit's files through the API instead, one request per file (large files are left out, as with the download). Bitbucket allows about a thousand API requests an hour, so for repositories with more than 2000 files Revue asks you to sign that account in with an API token to browse its code. Reviewing diffs and comments works either way.
Jira (Atlassian)#
- Go to https://developer.atlassian.com/console/myapps/ → Create → OAuth 2.0 integration.
- Name it
Revue, choose Resource-level access (the user picks the site on the consent screen), and accept the terms. - Open Permissions → Jira API → Add, then Configure, and add these classic scopes:
read:jira-work,write:jira-work,read:jira-user. - Open Authorization → OAuth 2.0 (3LO) → Add, enter the callback URL
http://localhost:47392/callback, and click Save changes. - Under Settings, copy the Client ID and Secret.
- Set them as
REVUE_JIRA_CLIENT_IDandREVUE_JIRA_CLIENT_SECRET. - To let anyone but you sign in, open Distribution and set Distribution status to Sharing.
Advanced: Jira details
- Don't add
offline_accessin the console. Revue asks for it when signing in, to get a refresh token. - For sharing, Atlassian asks for a vendor name, a privacy policy address and whether the app stores personal data. Revue stores tokens only on the user's own computer and has no server for them.
- At sign-in, Revue lists the Jira sites the account can reach. If there's more than one, type the site (
your-company.atlassian.net) in the form first.
Forgejo, Codeberg and Gitea#
Forgejo apps belong to one server, so you register one per server. Revue uses them as public clients with PKCE, so there's no secret.
- On the server, open Settings → Applications → Manage OAuth2 applications (on Codeberg:
https://codeberg.org/user/settings/applications). On your own server, an admin can instead create it for everyone under Site administration → Integrations → Applications. - Name it
Revueand enter the redirect URIhttp://127.0.0.1:47392/callback. - Untick Confidential client.
- Click Create application and copy the Client ID (a UUID). Ignore the secret.
- Set
REVUE_FORGEJO_CLIENT_IDS=https://codeberg.org=<client id>.
For several servers, separate the pairs with commas:
REVUE_FORGEJO_CLIENT_IDS=https://codeberg.org=<id>,https://git.example.com=<id>
For a server that isn't listed, users can make such an app on their own account (steps 1 to 4) and paste its client ID into the field the Forgejo sign-in form shows above Sign in with browser. The form walks them through the same steps.
Advanced: Forgejo details
- Revue asks for the scopes
read:user write:repository write:issue read:notification. Servers too old to know OAuth scopes grant full access instead. - Server addresses in
REVUE_FORGEJO_CLIENT_IDSare matched loosely:codeberg.organdhttps://codeberg.org/are the same. Pairs may also be separated by;or new lines.
Slack#
Slack is connected under Settings → Accounts, like the other services, and is offered on the welcome screen as an optional extra. Revue uses it to reply in a pull request's Slack thread when you submit a review. It signs in to the user's own Slack account (user scopes only, no bot) and posts as them.
- Create one Slack app from the manifest described on the Slack page.
- Under OAuth & Permissions, turn on PKCE.
- Check that the redirect URL is
http://localhost:47392/callback. - Open Manage Distribution and click Activate Public Distribution, so any workspace can connect it.
- Set
REVUE_SLACK_CLIENT_IDto the app's client ID. A PKCE app has no secret.
Advanced: a Slack app without PKCE, and other options
- Slack lets a PKCE app come back to
http://localhost, but only for user scopes, which is all Revue asks for. - Without PKCE, Slack sends the browser only to an HTTPS address. Then also set
REVUE_SLACK_CLIENT_SECRET, andREVUE_SLACK_REDIRECT_URLto an HTTPS page that forwards the browser tohttp://localhost:47392/callbackwith the same query. - When the browser can't get back to Revue, users can paste the address it ended on into the sign-in page, under the box that waits for the browser.
- Settings → Accounts → Slack → Use your own Slack app lets each user override the built-in app (client ID, secret, redirect URL), and a pasted user token (
xoxp-…) works without any app of Revue's.
Building your apps into a release#
Set the same variables in the environment of the build, and the binary carries them. In CI, store the IDs as variables and the secrets as secrets:
| Name | Kind |
|---|---|
REVUE_GITHUB_CLIENT_ID | variable |
REVUE_GITHUB_CLIENT_SECRET | secret |
REVUE_BITBUCKET_CLIENT_ID | variable |
REVUE_BITBUCKET_CLIENT_SECRET | secret |
REVUE_JIRA_CLIENT_ID | variable |
REVUE_JIRA_CLIENT_SECRET | secret |
REVUE_FORGEJO_CLIENT_IDS | variable |
REVUE_SLACK_CLIENT_ID | variable |
REVUE_SLACK_CLIENT_SECRET | secret (only for an app without PKCE) |
REVUE_SLACK_REDIRECT_URL | variable (only for an app without PKCE) |
REVUE_IMAGES_TOKEN | secret |
Anything left unset only means that service offers token sign-in alone.
Advanced: secrets in a desktop app
A secret built into a desktop app can be extracted from the binary, as with any native OAuth client. It identifies the app but grants nothing without a user's consent. If it leaks, rotate it in the service's console and publish a new build.
Translating#
Revue speaks English and Czech. The English text in the code is the key, and each other language is a JSON file in assets/i18n/ mapping English text to its translation.
- Find the strings:
cargo test i18nlists every string the code translates that the catalog lacks, and entries no longer used. - Add the translations to the language's file, e.g.
assets/i18n/cs.json. - Run
cargo test i18nagain until it passes. It also checks that each translation keeps its{placeholders}.
Debug builds also print i18n: no translation for … the first time a missing string shows.
Advanced: how strings are marked in the code
tr!("Sign out")for plain text.trf!("Sign out {name}?", name = login)with named placeholders. A translation may reorder them.trn!("{n} file", "{n} files", count)for counts.tr_mark!("Appearance")marks text in tables and constants; it's translated where it's shown, withi18n::tr.- Plurals are an object of forms keyed by the English plural. Czech uses
onefor 1,fewfor 2 to 4 andotherfor the rest. - The
"//"entry in a language file holds notes and the glossary for translators.
Advanced: adding a language
- Add
assets/i18n/<code>.json. - In
src/i18n.rs, add a variant toLanguageandLang, with its plural rule inplural. - Add its code to
Lang::from_code.
Releasing#
Releases are made by the maintainers. Either way, the Linux, Windows and macOS files go to the release on Forgejo, with notes made from the commits since the previous release.
- From Forgejo: Actions →
release→ Run workflow onmain, choosing patch, minor or major. - From a computer:
task deploy:patch(ordeploy:minor,deploy:major) builds all three platforms in Docker, and only when all of them succeed does it commit, tag, push and upload. It needs Docker, jq and curl, and reads the OAuth apps and aFORGEJO_ACCESS_TOKENfrom.env.
Pushing a tag doesn't start a release.
Both write the new version's section of CHANGELOG.md before building, and commit it in the release commit: the app shows the file in What's new (see Updates), and the release's notes list the same changes. task changelog writes the whole file from git: a section per vX.Y.Z tag, newest first, with the subjects of the commits since the tag before (merges and the Release X.Y.Z commits left out), and one for the version in Cargo.toml when it has no tag yet. So a commit's subject is what users read about it: write it as Area: what changes for the user, and give anything users don't see (tests, builds, releases, plans, docs, refactors) a Changelog: internal line, which leaves it out. To correct a commit already made, regroup or reword it in CHANGELOG.groups and run task changelog: the app reads the default branch's CHANGELOG.md before an update, so the correction reaches it, and task release:notes puts it on the releases.
Advanced: how each platform is built
- Linux: built in
rust:1-bookworm, so it runs on glibc 2.36 or newer (Debian 12, Ubuntu 22.04, and later). - Windows: cross-compiled with
cargo-xwin. GPUI's HLSL shaders come precompiled (regenerate them withtask windows-shaders). - macOS: cross-compiled as one universal binary (Apple Silicon and Intel) with
cargo-zigbuild, packaged asRevue.app. Its Metal shaders are compiled when it runs. rust-toolchain.tomlpins the toolchain the build containers use.- A release from Forgejo pushes its commit and tag with the repository secret
RELEASE_TOKEN, a personal access token (scopewrite:repository) of someone who may push tomain.