- Rust 97.2%
- JavaScript 1.7%
- CSS 0.6%
- Dockerfile 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .claude | ||
| .forgejo/workflows | ||
| assets | ||
| crates | ||
| deploy/web | ||
| docs | ||
| editors/zed | ||
| packaging | ||
| scripts | ||
| server | ||
| src | ||
| tests | ||
| vendor | ||
| web | ||
| .env.example | ||
| .gitignore | ||
| .mcp.json | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.groups | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| default.nix | ||
| flake.lock | ||
| flake.nix | ||
| global.env | ||
| LICENSE-APACHE | ||
| LICENSE-MIT | ||
| package.nix | ||
| README.md | ||
| rust-analyzer.toml | ||
| rust-toolchain.toml | ||
| Taskfile.yml | ||
Revue
A fast, native code review app for GitHub, Bitbucket Cloud and Forgejo, built with Rust and GPUI (Zed's GPU-accelerated UI framework, via gpui-kit). It doesn't use Electron or a webview.
Warning
This project is fully vibe coded. Its code, tests and docs are written by AI coding agents (Claude Code), directed by a developer rather than written by hand. Use it at your own risk.
Documentation: every feature and setting, with step-by-step guides, is at https://revue.cleangame.dev/docs, written in docs/ (start with docs/index.md).
Why
GitHub's review UI hides parts of the conversation ("Load more…", collapsed "hidden items"), loses context when commits are pushed, and makes reviewing a few new commits awkward. Revue is built around these goals:
-
No hidden conversations. It loads every thread, comment, review and commit, paginating each connection all the way through. Threads show inline in the diff and also in a threads panel. Threads that can't sit on a line in the current view (outdated, file-level, or written on other commits) show at the top of their file rather than disappearing.
-
Any range of commits. You can view:
- all changes,
- changes since your last review,
- changes since your last visit,
- a single commit (click it),
- or any range of commits (shift-click).
Range diffs hide files that only changed because the base branch was merged in. Existing threads are re-mapped onto older commits when their lines still exist there.
-
Drafts are never lost. Every pending comment and every half-written reply is saved to disk on each keystroke. If submitting fails, the drafts stay. Esc asks before throwing text away.
-
Comments written on any commit range still land. When you submit, Revue carries each comment's line forward to the PR head through the diff between the commit you commented on and the head. If the line has changed since, the comment becomes a file-level comment that quotes its original location. Nothing is dropped.
-
Fast:
- The UI is GPU-rendered and the diff is virtualized.
- Syntax highlighting uses tree-sitter in the background, for 35 languages.
- REST calls are cached with ETags (a
304doesn't count against the rate limit). - GraphQL results are cached on disk, so the inbox and PRs appear instantly at startup and then revalidate in the background.
- PRs waiting on you are prefetched, and so is any PR you hover in the inbox.
Features
-
Inbox: review requested, re-review (shows a dot when there are new commits since your review), your PRs, and PRs you're involved in. Each row shows CI status, review decision and draft counts.
-
Tabs: several PRs open at once. They're restored on restart.
-
Repositories: browse any repository without a PR (Ctrl+G, or pick one in Ctrl+K). Its tab lists:
- the history of any branch: click a commit for its diff, Shift+click another for the whole range, filter by message, author or hash;
- the branches, each one's diff against the default branch, with Open
pull request on those without one (the branch checked out in your
local clone first, pushed with
git push -uif it isn't yet, or a plaingit pushwhen it's ahead; never forced) and a link on those with one; - its pull requests, open, merged or closed, searchable;
- its code at any commit.
Clone on a repository's row clones it, signed in as the account it's listed for: its remote names that user (
https://login@github.com/…), so git's credential helper uses that account for every fetch and push. It goes into the folder set in Settings → Storage (one per service, over a default; asked when none is set), and is then used for worktrees and the Code tab. -
Diff:
- Split and unified modes, word-level inline highlights, and collapsible files.
- Viewed-file state syncs with GitHub and auto-collapses viewed files.
- Progress bar for viewed files.
-
Comments:
- Comment on a single line or on a range by dragging the gutter, or with the
+button. - "Suggest change" inserts a
```suggestionblock prefilled with the selected lines. - "Add to review" queues the comment as a draft; "Comment now" posts it straight away.
- Comment on a single line or on a range by dragging the gutter, or with the
-
Threads:
- Reply inline, and resolve or unresolve.
- The threads panel's tabs list All threads or those on the files on
screen (Visible); Show resolved and Show from others add
those, and a search finds threads by text, file or person.
]and[jump between open threads.
-
Conversation tab: the PR description and a comment box, then a timeline of comments, reviews, threads (with their diff-hunk context), force-pushes and commits, newest first. It's virtualized, with nothing collapsed.
-
Submit: Comment, Approve or Request changes, with a summary that is saved as you type.
Download
Revue's website, https://revue.cleangame.dev, has the latest version for
each platform on its downloads page.
It's served by Revue's own server (server/, see
server/DEPLOY.md), and task server:dev runs it on your
computer at http://localhost:7377.
Prebuilt builds are attached to every release:
| Platform | File | Install |
|---|---|---|
| Linux x86_64 | revue-vX.Y.Z-linux-x86_64.{deb,rpm,pkg.tar.zst} |
From the apt, dnf and pacman repositories (setup), or install the file |
| Linux x86_64 | revue-vX.Y.Z-linux-x86_64.AppImage |
chmod +x, run it; it updates itself |
| Linux x86_64 | revue-vX.Y.Z-linux-x86_64.tar.gz |
Extract, run task install (installs to ~/.local) |
| Windows x86_64 | revue-vX.Y.Z-windows-x86_64-setup.exe |
Run the installer |
| Windows x86_64 | revue-vX.Y.Z-windows-x86_64.zip |
Extract, run revue.exe |
| macOS (Apple Silicon + Intel) | revue-vX.Y.Z-macos-universal.dmg |
Open, drag Revue to Applications. It isn't notarized, so the first time, right-click Revue.app → Open |
| macOS (Apple Silicon + Intel) | revue-vX.Y.Z-macos-universal.zip |
Extract, move Revue.app to Applications (right-click → Open the first time, as above) |
With Nix, nix profile install git+https://forgejo.cleangame.dev/thatrichan/nix-revue installs the release binary (also on NUR as nur.repos.revue.revue-bin), or add this repository's flake as an input to build from source (see below). Without flakes, nix-env -if https://forgejo.cleangame.dev/thatrichan/revue/archive/main.tar.gz builds it from source, and pkgs.callPackage "${src}/package.nix" { } adds it to a NixOS or home-manager configuration. On Windows, scoop bucket add revue https://forgejo.cleangame.dev/thatrichan/scoop-revue then scoop install revue; on macOS, brew tap thatrichan/revue https://forgejo.cleangame.dev/thatrichan/homebrew-revue then brew install --cask thatrichan/revue/revue.
Running
On NixOS, or anywhere with Nix:
nix run . # build & run the packaged app
nix profile install . # install, including a desktop entry
From a checkout, every command is a Task task, the same on Linux, macOS and Windows (inside the Nix dev shell when there's Nix):
task dev # dev build of this checkout ("Revue Dev")
task dev -- https://github.com/owner/repo/pull/123 # open a PR directly
task dev:release # try out a release build, changing nothing (below)
task server:dev # Revue's server and website, at http://localhost:7377
task dev:demo # the app on mock services, no accounts needed (below)
task --list # the others: check, deploy:patch, shoot…
A release build uses the installed Revue's settings, sign-ins and data, so
task dev:release runs it with --immutable: on a copy of them, in a
temporary folder deleted on quit, with the credential store copied into
memory. It doesn't install updates or register itself for links either.
Renewed sign-in tokens are still saved for real, as the services stop
taking the ones they replace. revue --immutable does the same for any
build.
Without Nix you need Rust 1.90+ and the usual GPUI Linux dependencies (wayland, libxkbcommon, xcb, vulkan-loader, fontconfig, freetype).
Demo: no accounts needed
task dev:demo # or: revue --demo
--demo runs Revue against mock GitHub, Bitbucket Cloud, Forgejo, Jira and
Slack services on this computer (src/mock), signed in to all of them as a
made-up person at a made-up company: pull requests with real diffs, review
threads, CI checks, tickets and Slack threads, every name and line of it
fictional. Everything you do (comments, reviews, reactions, new pull
requests…) changes the mock and reads back. It runs on a new, empty
settings and data folder, deleted on quit, never on Revue's own: none of
your settings, sign-ins, credential store, caches, gh token or
GITHUB_TOKEN is read. Nothing leaves the computer either: every request
for anything but the mocks is refused, and git only works with local
repositories (each mock repository is a real local clone, so worktrees,
the Code tab and language servers work as with your own). The demo
starts with the app's default look; REVUE_DEMO_SETTINGS (JSON) is laid
over its settings, e.g. REVUE_DEMO_SETTINGS='{"dark": false}'.
Editors ask the demo too: revue --demo --pulls … (and every other
editor question, and --pull-request) goes to the demo's window, not
Revue's own, and with --headless and no demo running it's answered from
mock services of its own. REVUE_DEMO=1 in an editor's environment does
the same for the plain revue --pulls … its plugin runs.
task shoot -- OUTDIR --demo ::: STEPS… takes screenshots of the demo, and
task shoot:site all of the website's (server/assets/screenshots), dark
and light, in one go. task shoot:editors sets up the demo and a project
for the JetBrains IDE and Zed ones, taken by hand, and prints the steps.
Tests against the mock services
The mocks are stateful fakes of each service's HTTP API that the app's real clients talk to, so a test exercises everything above the wire:
let services = crate::mock::Services::start(); // every service, seeded
cx.update(|cx| services.install(cx)); // the app signed in to each
// …drive the UI, then check what the service holds:
let w = services.github.state.lock();
assert_eq!(w.pr("tidewater/atlas-api", 214).unwrap().reviews.len(), 3);
For one service, mock::github::start(seed::github()) and a client from
mock::github::client(&svc) (the same for bitbucket, forgejo, jira,
slack). Make a service fail with svc.faults.add(Fault::new("/pulls/1/files", 502, "…").times(2)), sign its account out with svc.revoke(), or change what
it holds (svc.state.lock(): someone else's comment, a push) to see the app
pick it up. Examples: src/mock/tests.rs, src/ui/mock_flows.rs, and each
service's own tests in src/mock/.
Opening pull requests from the browser
revue <PR link> opens a pull request, in the running Revue if there is one.
Under Settings → Apps, Register as handler makes revue:// links
open Revue (Windows and Linux; Revue.app declares them itself on macOS), and
the bookmarklet shown there sends the pull request page you're on to Revue as
revue://open?url=<link>.
To send someone a link that opens in Revue, use Copy Revue link (in the
pull request's header and its right-click menu in lists). It's the pull
request's address with Revue's server in front, e.g.
https://revue.cleangame.dev/github.com/acme/web/pull/12, so you can also
type one by hand. It opens Revue where it's installed and offers the page on
the web where it isn't.
Authentication
Sign in with as many GitHub, Bitbucket and Forgejo accounts as you like, on the
welcome screen or under Settings. Sign-ins are stored in
~/.config/revue/tokens/ (whose account, which server), so they sync along
with the config folder; their tokens (and OAuth refresh tokens) never do.
They stay on each computer, which signs in on its own: Atlassian, Forgejo
and Slack refresh tokens work once, so two computers sharing them would
sign each other out. By default they're in a file readable only by you
(mode 0600) in the local data folder: ~/.local/share/revue/sign-ins.json
on Linux, ~/Library/Application Support/revue/sign-ins.json on macOS,
%LOCALAPPDATA%\revue\sign-ins.json on Windows.
Settings → Storage → Sign-ins can keep the tokens in the system's
credential store instead (the switch is saved in that computer's
settings.local.json): the login keychain on macOS, a file encrypted for
your Windows user with DPAPI, the Secret Service (GNOME Keyring, KWallet…,
through libsecret's secret-tool) on Linux. macOS may ask once after each
update to let Revue use the keychain. Turned off, the tokens go back to the
local file and the store's entry is deleted. Where no store can be used (a
server without a desktop session, no secret-tool), the switch says so and
the tokens stay in the local file.
Turn on system credentials to
also use GITHUB_TOKEN / GH_TOKEN or the logged-in gh CLI.
REVUE_GITHUB_TOKEN, when set, is used alone.
Every service can also be signed in to with the browser (OAuth), as an
alternative to pasting a token; the token forms stay as they are. GitHub uses
its device flow (you type a code on github.com). Bitbucket, Jira and Forgejo
send the browser back to http://localhost:47392/callback (Atlassian) or
http://127.0.0.1:47392/callback (Forgejo), and Revue renews their
short-lived access tokens with the refresh token, saving both to the same
files. The button only shows for a service whose OAuth app Revue knows,
from these variables at run time or, else, when it was built. For local
runs, copy .env.example to .env and fill it in; task dev loads it.
Setup for each service is in docs/oauth-setup.md.
| Variable | For |
|---|---|
REVUE_GITHUB_CLIENT_ID, REVUE_GITHUB_CLIENT_SECRET |
GitHub OAuth app (device flow enabled; the secret renews sign-ins made on the web account) |
REVUE_BITBUCKET_CLIENT_ID, REVUE_BITBUCKET_CLIENT_SECRET |
Bitbucket OAuth consumer |
REVUE_JIRA_CLIENT_ID, REVUE_JIRA_CLIENT_SECRET |
Atlassian OAuth 2.0 (3LO) app |
REVUE_FORGEJO_CLIENT_IDS |
https://codeberg.org=<id>,https://git.example.com=<id> |
REVUE_SLACK_CLIENT_ID (and, without PKCE, _SECRET, REVUE_SLACK_REDIRECT_URL) |
Slack app (user scopes) |
For a Forgejo server not listed, the form asks for the client ID of an
OAuth2 application made on that server (public, redirect URI
http://127.0.0.1:47392/callback). Bitbucket only takes OAuth tokens on
api.bitbucket.org, so for an OAuth-signed Bitbucket account the Code tab
fetches a commit's files one by one through the API, for repositories of up
to 2000 files. Larger ones need an API token sign-in to browse their code.
Each account has a Track switch: tracked accounts fill the pull request list; the others still open PR links. A PR opens with the account whose list it came from, or else the first account that can see it.
Bitbucket Cloud
Revue also reviews Bitbucket Cloud pull requests, side by side with GitHub
ones. Add the account on the sign-in screen or in Settings → Bitbucket
account, with your Atlassian account email and an
API token
with these scopes: read:user:bitbucket, read:pullrequest:bitbucket,
write:pullrequest:bitbucket, read:repository:bitbucket,
read:workspace:bitbucket. It's kept like the other tokens (see
Authentication). You can use
Bitbucket without a GitHub account.
Open a PR with its link (https://bitbucket.org/ws/repo/pull-requests/12) or
from the inbox. A few things differ from GitHub:
- Bitbucket can't search pull requests across repositories, so "Needs my review" and "All in my repos" cover the 30 most recently updated repositories of the workspaces listed under Settings → Watched workspaces. With none listed, Revue uses the repositories of your own open PRs.
- Submitting a review posts its comments one by one, then the summary as a plain comment, then the approval or change request. If a comment fails, the ones already posted leave your drafts and the rest stay drafts.
- "Viewed" marks on files stay in Revue (Bitbucket has no API for them).
Forgejo
Revue reviews pull requests on Codeberg or your own
Forgejo server (Gitea works too, as it has the same API). Add the account on
the sign-in screen or under Settings → Forgejo accounts, with the server's
address and an access token (Settings → Applications on the server) with
read:user, write:repository and write:issue. It's kept like the
other tokens (see Authentication). You can add
accounts on several servers.
Open a PR with its link (https://codeberg.org/owner/repo/pulls/12) or from
the inbox. Some things work differently from GitHub:
- "All in my repos" lists the open PRs of the 30 repositories your own PRs, review requests and reviews are in.
- Forgejo comments sit on a single line, so a comment on a range of lines is posted on its last line. File-level comments go into the review summary.
- A reply is posted as a one-comment review on the thread's line, the way Forgejo's website does it.
- The API can't resolve conversations, so Revue only shows whether they're resolved. "Viewed" marks on files stay in Revue.
- Commit ranges ("since my review", single commits) are diffed by Revue from the files' two versions, since Forgejo's API doesn't return patches for them.
Jira
Connect Jira under Settings → Jira with your site, Atlassian account email
and a scoped API token
("Create API token with scopes", app Jira) with read:jira-work,
write:jira-work and read:jira-user. Classic tokens without scopes aren't
supported; Revue talks to Jira through Atlassian's API gateway. Links to tickets on that site then open in Revue instead
of the browser, in a tab grouped with the PR you clicked them in (closing
the PR closes them too), and the keys of its projects are linked
everywhere. A ticket tab shows its status, type, priority, assignee,
reporter, people fields such as Tester, epic or parent, sprint, labels,
components, description and comments. You can comment (Markdown), move the
ticket along its workflow (the transitions Jira offers from its current
status) and assign it to yourself. The token is kept like the others (see
Authentication).
Slack
Connect Slack under Settings → Accounts, like the other services (Sign in
with Slack in the browser, or a pasted user token; the welcome screen
offers it too, as an optional extra). The Submit review dialog then has Notify in Slack:
Revue finds the Slack threads that link to the pull request (Slack's search,
as you), you pick one and who to tag, and once the review is in it replies
there as you: "@Ann I approved your pull request #12 …". The author is found
by their email on the code host, or by Settings → Slack messages → People on Slack,
or you pick them from the channel's members. A failed reply doesn't fail the
review. A Slack threads tab (key 7) lists every thread linking to the
pull request, with Open in Slack (in Slack's app when it's installed,
Settings → Apps → Slack). Above them, Ask for a review requests
reviews again after changes (those who reviewed start picked) on the code
host and posts "@bob please review …" in a thread. In any comment, # and a letter suggest your
Slack channels; the pick goes in as a link ([#dev](…/archives/C…)), and a
#channel typed out becomes one when posted. A pull request opened from
Revue can be announced in a channel you pick: the ticket's link, with the pull request
and its reviewers tagged in its thread. Settings → Slack messages → Message formats
changes the wording of every message (placeholders like {pr_link},
{reviewers}), with a preview, and sets their language apart from the
app's. The preview draws :emoji:, the workspace's own too, flags names
that aren't emoji ("did you mean :balloon:?"), and : suggests them as
you type. Attach file… (or a drop) adds files to a message, a GIF on
"approved" say, kept in the config folder (up to 10 MB each) and posted
with the text as one message. Setup, scopes, formats and privacy:
docs/slack.md.
The token is kept like the others (see Authentication).
Web version
The same app runs in a browser: the library compiled to WebAssembly and
drawn by GPUI's web platform, served by revue serve: on the computer whose
accounts it uses, or hosted for many people, each with their own accounts.
task web:build # the bundle, into target/web-dist (PROFILE=dev: quicker)
task web:serve # revue serve --web-dir target/web-dist; open http://127.0.0.1:8421/
task web:check # only whether the browser build compiles
task web:build builds in Docker, so it needs nothing but Docker: a
rust:1-bookworm container with the wasm32-unknown-unknown target and the
wasm-bindgen CLI of the version in Cargo.lock, kept in revue-web-*
volumes with the build, so the next one only rebuilds what changed. With
rustup's wasm target and that wasm-bindgen installed, task web:bundle
does the same without Docker (task web:tools installs them on Debian or
Ubuntu, as root). It runs on plain stable Rust, single-threaded:
vendor/gpui-pre-web is GPUI's web platform without its multithreaded
feature, whose wasm_thread needs nightly. Without --web-dir, revue serve looks for
web/ next to its executable; with --demo (task web:serve -- --demo) it
serves the demo's mock services and accounts instead. task web:probe -- <url> [seconds] [shot.png] loads a page in headless Chromium (with deno) and
prints its console and a screenshot; task web:probe:hosted loads a signed-in
hosted session's PR list that way, and fails when the page throws, draws
nothing or doesn't load the PRs. What's left to do:
docs/web-plan.md.
Hosting it for a team. deploy/web has a Docker image of revue serve
with the bundle (task web:image) and a Compose example behind Caddy with
HTTPS (task web:up). Registering the OAuth apps, every setting, backups,
upgrades and the security model: docs/web-hosting.md.
A browser lends the page no fonts, so the bundle brings Noto Sans and Noto
Sans Mono (assets/fonts, SIL Open Font License), fetched at start; a font
chosen in Settings that the page doesn't have falls back to them. GPUI draws
with WebGL 2; add ?webgpu to the address to try WebGPU first (where a
browser's WebGPU misbehaves, the page stays blank and the console says
"device lost").
How it talks to the services. GitHub, Bitbucket, Forgejo and Jira don't
let other sites' pages call their APIs (Forgejo has CORS off by default), so
the page asks revue serve, on its own origin, and the server sends the
request on. Tokens never reach the browser: the server uses the accounts
signed in on its computer (the desktop app's sign-ins, GITHUB_TOKEN or the
gh CLI with system credentials on), hands the page a handle for each, and
signs every request for the account the page names. Sign in, or out, with
the desktop app there and reload the page.
Security. revue serve listens on 127.0.0.1:8421 and answers only to
that address by name (no DNS rebinding). --bind <address>[:port] listens
elsewhere, and then requires an access secret: made up and printed at start,
or given with --secret / REVUE_SERVE_SECRET. The page asks for it once
and keeps a session cookie (HttpOnly, SameSite=Strict); without it nothing
but the sign-in page answers. It isn't an open proxy: requests go only to the
hosts of its accounts (a signed one to its own account's origin; unsigned
GETs only for avatars, answered only with PNG, JPEG, GIF or WebP), cookies
and the page's Authorization header stay behind, and answers carry
nosniff, DENY framing, no-store caching and a Content Security Policy.
Only the page's own requests reach the proxy (not a link opened in the
browser), and what it answers is sandboxed and served as a download, never
a page of this origin.
It speaks plain HTTP: beyond a network you trust, put it behind an HTTPS
reverse proxy.
Hosted mode. revue serve --hosted (also chosen by --config <file.json>, or when REVUE_PUBLIC_URL or REVUE_CONFIG is set) serves
the web version to many people instead: each browser signs in to its own
accounts, and the server never reads its computer's sign-ins. The basics:
REVUE_PUBLIC_URL=https://revue.example.com \
REVUE_SESSION_KEY="$(openssl rand -hex 32)" \
REVUE_DATA_DIR=/var/lib/revue REVUE_TRUST_PROXY=1 \
REVUE_GITHUB_CLIENT_ID=… REVUE_GITHUB_CLIENT_SECRET=… \
REVUE_FORGEJO=https://codeberg.org REVUE_JIRA_SITES=acme.atlassian.net \
revue serve --hosted --web-dir target/web-dist
REVUE_PUBLIC_URL (with a path, such as https://example.com/revue, it
serves everything under that path) and REVUE_SESSION_KEY (32+
characters; changing it signs everyone out) are required; it listens on
REVUE_BIND (default 0.0.0.0:8421). A service with an OAuth app configured
(REVUE_<GITHUB|BITBUCKET|JIRA>_CLIENT_ID and _CLIENT_SECRET, or
<url>=<id>[:<secret>] in REVUE_FORGEJO) signs in with it, coming back
to <public url>/auth/<service>/callback; any service also takes a pasted
token, which the server checks. Forgejo servers and Jira sites are only
those listed. Sessions (a cookie, 30 days by default, REVUE_SESSION_DAYS)
and their tokens are kept in REVUE_DATA_DIR, encrypted with AES-256-GCM.
Requests that change anything need the page's origin and the session's
anti-forgery token, the Host must be the public address's (/health
excepted), each session is rate limited (REVUE_RATE_LIMIT a minute), and
the server reaches no private or loopback address unless
REVUE_ALLOW_PRIVATE_HOSTS names its host. It's meant to run behind an
HTTPS reverse proxy (with REVUE_TRUST_PROXY=1), which also shields it
from slow and numerous clients. Every variable: revue serve --hosted --help; the design: docs/web-plan.md.
Not in the browser. Everything that needs the computer: local clones and
worktrees, source checkouts (so the Code tab and Find in Files), language
servers and code navigation, "Open in" apps, terminals and assistants,
revue:// links, self-update and crash reports, the Revue account and
Slack, and signing in with the local revue serve (its computer's accounts
are used; the hosted mode has its own sign-in). These are hidden, or say
"Not available in the browser". Diffs are syntax highlighted as on the
desktop (the tree-sitter grammars are built for wasm32), but for C++, C#,
Kotlin, Scala and Swift, shown plain: their grammars would be most of the
page's download. Settings and each PR's state are kept in the page's
localStorage, the HTTP cache in memory.
With the local revue serve, OAuth tokens it renews are saved for the
desktop app too, but a desktop app running at the same time may need to
sign in again (refresh tokens are single-use).
Releasing
A release is made in one of two ways, and pushing a tag starts neither. Both
run the same tasks from Taskfile.yml (Task):
- From Forgejo: Actions →
release→ Run workflow onmain, choosing patch, minor or major..forgejo/workflows/release.ymlrunstask version:<bump>andtask release:tag(commit "Release X.Y.Z", tag vX.Y.Z, push), thentask release:build:<platform>andtask release:uploadin a container per platform on the Forgejo runner. Forgejo doesn't let a run's own token push, so the release commit and tag are pushed with the repository secretRELEASE_TOKEN: a personal access token (scopewrite:repository) of someone who may push tomain. - From your computer:
task deploy:patch(ordeploy:minor,deploy:major) raises the version, thentask buildruns the threerelease:build:*tasks in parallel, each in the same Docker image as in CI. Only when all three succeed does it commit, tag, push and upload. The OAuth apps and aFORGEJO_ACCESS_TOKENthat can write releases come from.env.
Either way the files go to the Forgejo release for that tag, created with notes made from the commits since the previous tag.
- Linux: built natively in
rust:1-bookworm, so it needs glibc 2.36 or newer. The binary links only libc, libxcb and libxkbcommon; Wayland, Vulkan and fontconfig are loaded at runtime. - Windows: cross-compiled with
cargo-xwin, which supplies clang-cl and the MSVC CRT and Windows SDK.- GPUI normally compiles its HLSL shaders with
fxc.exe, and only when the build host is Windows. Revue therefore patchesgpui-pre-windows(vendor/) to use shader bytecode precompiled with the real fxc.exe under Wine. Regenerate it withtask windows-shaders. - The icon is embedded and no console window opens.
- GPUI normally compiles its HLSL shaders with
- macOS: cross-compiled as a universal binary with
cargo-zigbuildand the macOS SDK.- GPUI's
runtime_shadersfeature compiles the Metal shaders at runtime, and a patchedgpui-pre-applebuild script checks the target OS instead of the host's. - The output is packaged as
Revue.app.
- GPUI's
rust-toolchain.toml pins the toolchain the containers use.
CI (.forgejo/workflows/check.yml) runs task check:ci on every push:
rustfmt, clippy --all-targets -D warnings, the tests and a release build.
Its web-probe job, alongside, builds the browser version (task web:tools,
task web:bundle) and runs task web:probe:hosted on it in headless
Chromium; the screenshot is the run's hosted-pr-list artifact.
Pushing doesn't run it; run task check:quick by hand before a
push when you want the same check locally.
Keyboard
| Key | Action |
|---|---|
Ctrl+K / Ctrl+L / Ctrl+O |
Search PRs and repositories, or open a PR by link or owner/repo#123 |
Ctrl+R / F5 |
Refresh |
Ctrl+Tab, Ctrl+W |
Next PR tab, close tab |
Ctrl+B / Ctrl+H |
Go to the pull request list |
1 2 3 |
Files / Conversation / Commits |
j / k |
Next / previous file |
n / p |
Next / previous change |
] / [ |
Next / previous open thread |
v |
Toggle viewed and jump to the next file |
a / r / l |
All changes / since your review / since last visit |
, / . |
Step to older / newer single commit |
c |
Comment on selected lines |
s |
Split / unified |
t |
Threads panel |
z / Shift+Z |
Collapse / expand all files |
Ctrl+Enter |
In a comment box: add to review or send reply. Otherwise: open submit |
Esc |
Close an empty composer |
Ctrl+Shift+T |
Reopen the tab closed last (again: the one before) |
Ctrl+Shift+L |
Light / dark |
Ctrl+/, F1 |
All shortcuts |
Single-key shortcuts are turned off while a text field has focus.
Every shortcut can be changed in Settings → Keyboard shortcuts (click a
key, press the new one; Esc cancels, Backspace removes it), with a second
key, a reset per shortcut and for all. A key another shortcut already has
where this one works is caught first: replace it there or swap the two.
Only the changed shortcuts are stored, under keybindings in the settings
(and in an export).
Keyboard layouts that type letters or other characters on the number row
and punctuation keys (Czech, Slovak, German, French, Hungarian…) get the
key in the same place instead of one they type only with Shift or AltGr:
on a Czech keyboard Ctrl+é resets the text size (Ctrl+0 on US),
ě š č… pick a PR's tabs, ) goes to the next thread. On Linux
Revue knows these layouts by the name xkb reports (Czech, German…);
on macOS and Windows GPUI matches the defaults by their place on a US
keyboard itself. The dialog, the badges of "hold Alt", tooltips and
Settings show the keys that work on the layout in use, and the keys
picked in Settings always win.
Layout
src/
main.rs the desktop binary: runs the library (lib.rs)
lib.rs app bootstrap, --probe CLI for API diagnostics, `serve`
web.rs the browser build's entry (wasm), web_api.rs its server API
serve/ `revue serve`: the web bundle and the proxy to the services
github/ client (ETag + disk cache, tokio), GraphQL queries, models, API ops
bitbucket/ Bitbucket Cloud API 2.0, mapped onto the same models
forgejo/ Forgejo / Gitea API v1 (Codeberg or self-hosted), same models
provider.rs routes each PR operation to GitHub, Bitbucket or Forgejo
diff.rs hunk parsing, line mapping across commits, GitHub patch adapter
review.rs drafts → review comments (re-anchoring to the PR head)
store.rs drafts, reply drafts, settings, token (all on disk)
ui/ app shell, inbox, PR view, threads/timeline, login, tests
cargo test runs the unit tests and a headless UI test. The UI test opens a
real GPUI window and checks:
- key dispatch,
- that single-key shortcuts don't fire while you type,
- that drafts persist,
- that Esc never discards text.
Settings, sign-ins, favorites and each PR's drafts and viewed files live in
~/.config/revue on every platform ($XDG_CONFIG_HOME/revue when set):
one folder to back up or sync between computers (Syncthing, a dotfiles
repo). Older versions kept them in ~/.local/share/revue; they move over
on first start. What Revue remembers rather than what you set (recent and
open tabs, pinned tabs, the pull request list's filters, panel widths) is
in state.json there, apart from settings.json. Machine-local state
stays in ~/.local/share/revue, and the HTTP cache, source checkouts and
settings-cache.json (language servers detected per repository, ones
stopped by hand) in ~/.cache/revue. Dev builds (task dev) use revue-dev everywhere instead, so they never touch
the installed app's accounts, drafts or settings.
Settings for one computer
Some settings belong to one machine: local clones, editor and terminal,
fonts, text size, language servers. They can live in
settings.local.json in the machine-local folder
(~/.local/share/revue on Linux, see Settings → Storage → Settings files),
which is never synced and beats the shared settings.json:
- each setting (top-level key) in it replaces the shared one; objects
(
local_repos,lsp_choices…) merge key by key, so the shared entries still show unless overridden;nullputs a setting back to its default; - a setting it has is saved there when changed, as what differs from the
shared value; the shared file keeps its own value for your other
computers. Everything else is saved to
settings.jsonas before; - the This computer / Shared switch on a settings card moves its settings into the local file or back, keeping their current value.
{
"text_size": 110,
"local_repos": { "owner/repo": "/home/me/src/repo" }
}
Languages
Revue speaks English and Czech. Settings → Appearance → Language picks one,
or System (the default) follows the computer: LANGUAGE, LC_ALL,
LC_MESSAGES or LANG on Linux, the preferred languages on macOS and the
display language on Windows; anything Revue doesn't have shows in English.
The change applies at once; text already laid out in open fields and
dialogs follows after a restart (the card offers one).
Translating
The English text in the code is the key (see src/i18n.rs):
tr!("Sign out")for plain text,trf!("Sign out {name}?", name = login)with named placeholders (a translation may reorder them), andtrn!("{n} file", "{n} files", count)for counts;tr_mark!("Appearance")marks text in tables and constants, translated where it's shown withi18n::tr.
Each language is a JSON file in assets/i18n/, English text → translation;
plurals are an object of forms (Czech: one for 1, few for 2–4, other
for the rest), keyed by the English plural. The "//" entry holds notes and
the glossary for translators.
- Missing strings:
cargo test i18nlists every string the code translates that the catalog lacks (and entries no longer used), and checks that each translation keeps its{placeholders}. Debug builds also printi18n: no translation for …the first time such a string shows. - Another language: add
assets/i18n/<code>.json, a variant toLanguageandLanginsrc/i18n.rswith its plural rule inplural, and its code toLang::from_code.
License
Licensed under either of Apache License 2.0 or
MIT, at your option. vendor/gpui-pre-windows,
vendor/gpui-pre-apple and vendor/gpui-pre-web are patched copies of GPUI
crates and keep their Apache-2.0 license.