Revue
DocumentationBuilding and contributing

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#

  1. Clone the repository:

    git clone https://forgejo.cleangame.dev/thatrichan/revue.git
    cd revue
    
  2. 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 --immutable does 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:

TaskWhat it does
task devBuild and run a dev build ("Revue Dev"); arguments after -- go to Revue
task dev:releaseBuild and run a release build on a copy of your data (--immutable)
task check:ciThe checks: workflow YAML, rustfmt, clippy with warnings as errors, and the tests
task server:devRun revue-server at http://localhost:7377 (see Self-hosting revue-server)
task shoot -- …Take screenshots of Revue in a headless session (Linux)
task versionPrint 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, or python3-yaml). Without Python and PyYAML, the workflow YAML check is skipped with a note; CI still runs it.
Advanced: other tasks
  • task icons regenerates every icon format from assets/revue-icon.png, including the green dev icons.
  • task file-icons -- <downloads folder> vendors the file-type icon themes and regenerates src/file_icons_table.rs.
  • task windows-shaders precompiles GPUI's Windows shaders (with fxc.exe under Wine). Only needed after updating the patched GPUI Windows crate.
  • task package PLATFORM=linux|windows|macos BIN=<path> packages a built binary into target/release-dist.
  • task build and task release:build:linux|windows|macos build 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:forgejo and task mcp:github start 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.
  • --size sets the window size (default 1920x1200), --light uses the light theme, --clean signs in only with a REVUE_GITHUB_TOKEN you pass explicitly, --cache starts from a copy of your HTTP cache (faster, but it holds your pull requests; not with --clean), --settings passes settings as JSON, and --demo runs 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,Y or drag:X1,Y1,X2,Y2.
  • It runs target/release/revue, or the binary in REVUE_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.

  1. Copy the template:

    cp .env.example .env
    
  2. Fill in the values you have. Leave the others empty.

  3. Run task dev. It loads .env into 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.

VariableFor
REVUE_GITHUB_CLIENT_ID, REVUE_GITHUB_CLIENT_SECRETGitHub sign-in in the browser (the secret renews sign-ins made on the web account)
REVUE_BITBUCKET_CLIENT_ID, REVUE_BITBUCKET_CLIENT_SECRETBitbucket sign-in in the browser
REVUE_JIRA_CLIENT_ID, REVUE_JIRA_CLIENT_SECRETJira sign-in in the browser
REVUE_FORGEJO_CLIENT_IDSForgejo, Codeberg and Gitea sign-in in the browser
REVUE_SLACK_CLIENT_ID, REVUE_SLACK_CLIENT_SECRET, REVUE_SLACK_REDIRECT_URLConnecting Slack
REVUE_IMAGES_TOKENUploading 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 .env with task 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.

ServiceHow sign-in worksYou need
GitHubDevice flow: you type a code on github.comClient ID (and its secret, to renew sign-ins made on the web account)
Bitbucket CloudBrowser comes back to http://localhost:47392/callbackKey and secret
Jira (Atlassian)Browser comes back to http://localhost:47392/callbackClient ID and secret
Forgejo, Codeberg, GiteaBrowser comes back to http://127.0.0.1:47392/callback (with PKCE)A client ID per server
SlackBrowser 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#

  1. 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.
  2. 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)
  3. Click Register application.
  4. Tick Enable Device Flow and save.
  5. Copy the Client ID (it starts with Ov23li) and set it as REVUE_GITHUB_CLIENT_ID.
  6. 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_SECRET too. 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#

  1. In a workspace you administer, open Workspace settings → Apps and features → OAuth consumers → Add consumer (https://bitbucket.org/<workspace>/workspace/settings/api).
  2. 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)
  3. Click Save.
  4. Expand the consumer to see its Key and Secret.
  5. Set REVUE_BITBUCKET_CLIENT_ID to the Key and REVUE_BITBUCKET_CLIENT_SECRET to 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)#

  1. Go to https://developer.atlassian.com/console/myapps/ → Create → OAuth 2.0 integration.
  2. Name it Revue, choose Resource-level access (the user picks the site on the consent screen), and accept the terms.
  3. Open Permissions → Jira API → Add, then Configure, and add these classic scopes: read:jira-work, write:jira-work, read:jira-user.
  4. Open Authorization → OAuth 2.0 (3LO) → Add, enter the callback URL http://localhost:47392/callback, and click Save changes.
  5. Under Settings, copy the Client ID and Secret.
  6. Set them as REVUE_JIRA_CLIENT_ID and REVUE_JIRA_CLIENT_SECRET.
  7. To let anyone but you sign in, open Distribution and set Distribution status to Sharing.
Advanced: Jira details
  • Don't add offline_access in 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.

  1. 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.
  2. Name it Revue and enter the redirect URI http://127.0.0.1:47392/callback.
  3. Untick Confidential client.
  4. Click Create application and copy the Client ID (a UUID). Ignore the secret.
  5. 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_IDS are matched loosely: codeberg.org and https://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.

  1. Create one Slack app from the manifest described on the Slack page.
  2. Under OAuth & Permissions, turn on PKCE.
  3. Check that the redirect URL is http://localhost:47392/callback.
  4. Open Manage Distribution and click Activate Public Distribution, so any workspace can connect it.
  5. Set REVUE_SLACK_CLIENT_ID to 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, and REVUE_SLACK_REDIRECT_URL to an HTTPS page that forwards the browser to http://localhost:47392/callback with 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:

NameKind
REVUE_GITHUB_CLIENT_IDvariable
REVUE_GITHUB_CLIENT_SECRETsecret
REVUE_BITBUCKET_CLIENT_IDvariable
REVUE_BITBUCKET_CLIENT_SECRETsecret
REVUE_JIRA_CLIENT_IDvariable
REVUE_JIRA_CLIENT_SECRETsecret
REVUE_FORGEJO_CLIENT_IDSvariable
REVUE_SLACK_CLIENT_IDvariable
REVUE_SLACK_CLIENT_SECRETsecret (only for an app without PKCE)
REVUE_SLACK_REDIRECT_URLvariable (only for an app without PKCE)
REVUE_IMAGES_TOKENsecret

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.

  1. Find the strings: cargo test i18n lists every string the code translates that the catalog lacks, and entries no longer used.
  2. Add the translations to the language's file, e.g. assets/i18n/cs.json.
  3. Run cargo test i18n again 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, with i18n::tr.
  • Plurals are an object of forms keyed by the English plural. Czech uses one for 1, few for 2 to 4 and other for the rest.
  • The "//" entry in a language file holds notes and the glossary for translators.
Advanced: adding a language
  1. Add assets/i18n/<code>.json.
  2. In src/i18n.rs, add a variant to Language and Lang, with its plural rule in plural.
  3. 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 on main, choosing patch, minor or major.
  • From a computer: task deploy:patch (or deploy: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 a FORGEJO_ACCESS_TOKEN from .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 with task windows-shaders).
  • macOS: cross-compiled as one universal binary (Apple Silicon and Intel) with cargo-zigbuild, packaged as Revue.app. Its Metal shaders are compiled when it runs.
  • rust-toolchain.toml pins 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 (scope write:repository) of someone who may push to main.

More#