Radicle Desktop
Radicle is a way to collaborate on code without a central server: your repositories, issues and patches live on your own machine and replicate peer to peer. Radicle Desktop is the native app for all of that: browsing repos, filing issues, and reviewing patches with your own node running underneath.
Role: Visual Design, Design System, Code
I’ve worked at Radicle since 2020. Early on I designed Radicle’s brand, website and first app, and in 2025 I turned to the desktop app.
This time I didn’t hand anyone a Figma file. I designed directly in the code and sent my work as patches, Radicle’s version of pull requests. So the tool I was designing was also the tool I used to ship the designs, and the maintainers (mostly Rudolfs, who reviewed an absurd amount of my work) reviewed my design decisions the same way they review everyone’s code. Every annoying thing I ran into while using Radicle was something I could just go fix.
Design system
Radicle got a new brand and the start of a new design system, which we developed together with the studio A Color Bright. My part was taking that system from a set of design files to two shipping apps, and growing it as the apps needed more than the first version covered.
Color works in two layers. Underneath are primitive neutrals: an opaque ramp and a transparent ramp, each in a light and a dark version. On top of those sit semantic tokens with names that say what they’re for: surfaces (base, canvas, subtle), text (primary down to quaternary), borders, feedback colors, and brand. A component never asks for a hex value. It asks for “subtle surface” or “tertiary text”, and switching between light and dark mode only swaps what sits underneath.
--color-surface-baselight-50dark-0--color-surface-canvaslight-0dark-50--color-surface-subtlelight-100dark-100--color-border-subtlelight-150dark-150--color-text-tertiarylight-600dark-600--color-text-secondarylight-700dark-700--color-text-primarylight-900dark-900--color-surface-brandblue-500blue-500Type is just as small a set. The interface uses one typeface, Booton, in three weights: regular, semibold, and a medium that sits at 468 instead of the usual 500, so it reads as a step up from body text without competing with headings. There are seven heading sizes and three body sizes, and each token is a full font shorthand (size, line height and weight together), so a component picks one name and gets the whole style. Code uses JetBrains Mono, and people can switch it to their system monospace in settings.
--txt-heading-xxl32/36 · 600Offline-first sync--txt-heading-xl28/32 · 600Offline-first sync--txt-heading-l22/28 · 600Offline edits lost when a peer reconnects--txt-heading-m18/20 · 600Offline edits lost when a peer reconnects--txt-heading-s16/20 · 600Offline edits lost when a peer reconnects--txt-heading-xs14/20 · 600Offline edits lost when a peer reconnects--txt-heading-xxs12/16 · 600Offline edits lost when a peer reconnects--txt-body-l-regular16/24 · 400Queued ops are replayed after the handshake finishes.--txt-body-m-regular14/20 · 400Queued ops are replayed after the handshake finishes.--txt-body-m-medium14/20 · 468Queued ops are replayed after the handshake finishes.--txt-body-m-semibold14/20 · 600Queued ops are replayed after the handshake finishes.--txt-body-s-regular12/16 · 400Queued ops are replayed after the handshake finishes.--txt-code-regular14/20 · 400git push rad HEAD:refs/patchesA system is also a set of rules. Buttons are a good example: there is very rarely a primary action on a screen, so a filled brand button is saved for the one thing you came to do (Review on a patch, New issue on the issue list), and everything else in the top bar is a quiet ghost button. Every variant and size lives in one place so nobody has to guess.
The same tokens build everything else: status chips that say where a patch or issue stands, badges, banners, form controls, menus, and an icon set drawn on a 16px grid to match.
Useful information that users should know, even when skimming content.
Helpful advice for doing things better or more easily.
Key information users need to know to achieve their goal.
Urgent info that needs immediate user attention to avoid problems.
Advises about risks or negative outcomes of certain actions.
The payoff has been bigger than I expected. Because every color has a name that says what it’s for, and every component follows the same few rules, the system is easy to build with, for people and for AI. The team builds a lot of Radicle with Claude Code, and a good design system turns out to be exactly what it needs: it picks the right token and the right button on its own. Most of the time developers can build new features with very little back and forth with me, and my job shifts from drawing every screen to reviewing and refining.
I also updated the app icon to the new brand.
Patches and code review
This was the big one. Patches are Radicle’s pull requests, and they are the heart of any code collaboration tool. Redesigning them took about five weeks of hands-on work, roughly 180 commits, and 52 revisions of the patch itself before it merged in August. It’s the part of this whole project I’m proudest of.
The old patch page made reviewing feel like doing your taxes. Revisions lived in a sidebar. Comments lived in one place and everything else (new revisions, reviews, merges) lived somewhere else, so you had to piece together what happened yourself. Leaving a review meant leaving a pile of separate comments and hoping they added up to an opinion.
One timeline
The core idea is simple: a patch is a story, so tell it in order. The new Activity tab is a single timeline where everything that happened to a patch shows up when it happened. Opening it, every new revision and its commits, comments, reviews, label and state changes, and finally the merge.
Everything hangs off one continuous rail, and every row puts its icon on that rail, so your eye can run straight down the history. The typography does a lot of quiet work: the verbs (“pushed”, “commented”, “accepted”) are set in a lighter tertiary color and the things that matter, the people and the content, carry the weight. It sounds small, but a timeline where every word is the same weight is a wall.
Anything that can expand tells you so. Hover a revision and its icon turns into an expand arrow and the row gets a background. Only rows that actually toggle get that hover state, so you never wonder if something is clickable. Click it and it opens in place with its description, the commits that are new in that revision, and a preview of the diff, animated open with a CSS grid transition.
Reviews became first-class citizens of the timeline. Each one shows as a pill colored by its verdict (green for accepted, red for rejected, grey when someone only left comments) with the summary right next to it. Expand it and the reviewer’s code comments sit nested underneath, each trimmed to just the lines it’s about, with a toggle for more context.
Changes and reviews
The Changes tab is the diff, built on the Pierre diff engine, with a revision picker, diff settings, and a way to mark files as reviewed as you work through them, so you always know how far along you are.
Reviews work the way you’d expect from GitHub, which honestly is the point. Click a line of code and leave a comment, and you’ve started a draft review. A bar floats in at the bottom of the window with your pending comments and your file progress. When you’re done you publish everything at once with a verdict. And if you just want to leave a quick note on a line without the whole ceremony, you can do that too.
The header
The top of the page answers “what is this and where is it at” before you scroll. The state (open, draft, archived) is a dropdown right next to the title, and authors can change the state of their own patches. Underneath, one row of metadata leads with the diff stats, which link straight to the Changes tab, then the author, reviewers and labels.
The conversation
And then the human part. Replies are indented in their own boxes so threads read as threads. Reactions show who reacted, highlight your own, and work on code comments and the patch description too.
Action buttons only show up on hover, so a long thread isn’t a wall of icons. Hover anyone’s name for a card with what they’ve been working on. And the comment box stays open at the bottom of the page instead of hiding behind a button.
Issues that match patches
Once patches looked like that, the issue page looked like it came from a different app. I rebuilt it to match: the sidebar is gone, the state control sits next to the title, and the author, labels and assignees form a row of chips underneath. The description gets its own block and the timeline leads with who opened the issue. Authors who aren’t maintainers can now close their own issues, which seems obvious but wasn’t possible before.
The sidebar
The sidebar is where you live in Radicle Desktop: your inbox, your repos, and the issues and patches inside them. It was a fixed column that only got longer as you seeded more repos. I rebuilt it across a handful of patches so it gets out of your way when you want it to and stays organized when you don’t.
Collapse it
The sidebar collapses to a narrow rail of icons. There are two ways to do it, so whatever your hand is already doing works:
- Press Cmd+B (Ctrl+B on Linux and Windows).
- Click the sidebar button next to Back and Forward.
The rail keeps only what you reach for all day: Back, Forward and Reload, the inbox, your pinned repos, and the Issues and Patches of the repo you’re in. Everything else (your identity, the full repo list, the guide, settings, node status, counters) waits in the expanded view. Every row is the same square, so an icon never moves depending on the label next to it, and the column of icons lines up with the macOS window buttons. Hover anything on the rail for a tooltip with its name, whether it’s private, and how many open issues or patches it has. Private repos get a small lock badge on their avatar.
Resize it
Drag the edge to make the sidebar anywhere from narrow to wide. The edge tracks your pointer exactly, with no animation lagging behind. Keep dragging past the minimum and it snaps closed into the rail. Drag back out and it opens again, all in a single gesture, so you can close and reopen it without letting go. And if you pull on the collapsed rail but not far enough, it stretches a little and eases back, so it feels like it’s resisting you rather than ignoring you. Both the width and whether it’s collapsed are remembered next time you open the app.
Organize your repos
- Pin the repos you care about. Pinned repos sit on top, and you can drag them into whatever order you like.
- Cmd+1 through Cmd+9 jump to a repo by its position in the sidebar.
- Filter the list by typing. The filter button slides over and turns into a close button while it’s open, so it’s clear how to get out.
- Right-click a repo to stop seeding it or view its identity document.
- The Guide button can be hidden once you know your way around, with a quick confirmation and a way to bring it back in Settings.
Motion and rhythm
When you open a repo, its Issues and Patches unfurl underneath it. The repo list rolls open and closed the same way. Collapsing the sidebar crossfades between the two views instead of trying to morph every row, label and button into its counterpart, which looked busy and felt slow.
Inbox
The inbox was one very long list. If you’d been away for a week it was hundreds of rows of “someone left a comment.” The redesign adds a Latest view sorted by time, collapses long runs of notifications so the overview is actually an overview, and adds an Involves me filter for things you opened, were assigned to, or took part in.
Writing
A lot of Radicle is writing: issues, patch descriptions, comments,
reviews. So the editor should feel like every other good Markdown
editor. Cmd+B, Cmd+I, Cmd+E and Cmd+K format your selection.
Pasting a URL over selected text makes a link. Task list
checkboxes are clickable now instead of making you edit
- [ ] by hand. Mermaid diagrams, GitHub style alerts
and images in READMEs all render.
And keyboard shortcuts everywhere, with a ? modal that
lists them all.
Smart links
On GitHub, a link to an issue is a URL on github.com. Radicle has no github.com. The same repo might be on a dozen seeds and viewed through any number of explorers, so a link to one website is a link that can rot. I wanted references that point at the thing, not at a website that happens to show it.
So references are stored as rad: URIs (and
did:key: for people), following Radicle’s own spec for
them, and the app works out where to show them.
Writing them
Nobody should have to type a URI. In any comment box,
@ suggests people, issues and patches, and
# narrows that down to just issues and patches. Paste a
Radicle ID, a commit hash, a person’s ID or an explorer URL, and the
app offers to turn it into a link labelled with the real title or
name. Short commit hashes get expanded so they stay unique.
Reading them
Repos, issues, patches, commits and people render as chips, so a
reference reads as what it is. Files (down to line ranges),
branches, tags and lists render as labelled links. Click one and
it opens right in the app if the repo is on your machine, and in
your explorer of choice if it isn’t. Right click for the explorer
link or the raw rad: URI.
Opening them from anywhere
The app also registers itself for rad: links, so one
clicked in a terminal, a chat app or a browser opens straight in
Radicle Desktop. That’s the payoff for storing links this way:
they work everywhere, and they never point at a server that might
not be there tomorrow.
Making the network visible
The hardest UX problem in a peer to peer app is that nobody knows where their stuff actually is. Did my patch make it to anyone? Is this repo up to date? Who’s allowed to change it? A bunch of my work was about turning invisible network state into something you can see.
The tiny Online/Offline popover became a node window that answers the real question, which is whether your work has reached the network: “Latest patch reached 11 nodes.” You can see and change a repository’s seeding policy, described by whose work you’ll get rather than by refs. And you can set your preferred seeds and which explorer links open in.
Every repository now has a readable identity document: who the delegates are, how many have to sign off on a change, and a timeline of every change to those rules, including who signed and who didn’t. And Releases brought Radicle’s new artifact system to the desktop app, so you can publish a release with files attached and download them verified against their content ID.
One identity, many devices
Here’s a problem you don’t have on a normal website: on Radicle, you are a key. There’s no account on a server, no password, no “log in on your new laptop”. Your identity is a cryptographic key that lives on one machine. So your laptop and your desktop are two different people as far as the network is concerned, and if you lose the machine, you lose yourself.
The protocol team proposed a fix: make a person a repository. Your profile and the set of keys allowed to act as you live in a repo of their own, replicated like everything else, and changing it takes signatures from your other devices. It’s an elegant idea and a really hard thing to explain. My job was to figure out what it would feel like to use, before anyone built it.
So I built a click-through prototype inside the real app, running on fake data, that walked through every flow in the spec. My rule was that nothing goes in the prototype unless it’s planned for the first version, so the team was reacting to the real thing and not a wish list.
Your profile, as others see it
A profile has a name, a bio, links and an avatar, like anywhere else. The twist is that it’s published to the network and replicated to everyone who follows you, so old values stay in the history forever. I put a live preview right above the form, showing your hover card and your Explorer page, so what you edit and what other people see are the same thing. And the form tells you plainly that what you write is permanent, before you write your home address in there.
Keys and two kinds of agreement
This is where it gets subtle. There are two separate rules, and mixing them up would be dangerous. Changing who you are (adding or removing a device) needs a majority of your controlling keys. Changing your profile (your bio, your avatar) only needs the signatures your profile requires. The Keys page keeps those two apart visually so nobody thinks updating their bio needs three devices, or that adding a device only needs one.
Keys also come in two kinds. Some can act as you, some can change who you are, and some do both. A key that can change your identity but never sits on a device is a recovery key. The list shows each key’s role, and if you have fewer than three controlling keys the app warns you, because losing one device shouldn’t lock you out.
Adding a device
Adding a device goes in one direction only. On the new machine you run a command that prints an enrollment code. On a device you already trust, you approve it, either from a notification or by pasting the code. No comparing fingerprints, no two-way handshake.
A recovery key you can actually use
The spec asks apps to make an offline recovery key easy to set up, so it’s a short guided flow. You get a recovery phrase, confirm you’ve stored it somewhere safe, and sign it into your identity. If your identity needs more than one device to agree, the change is proposed and waits for your other devices to approve it.
Boards
Seeing your work as a board, the way you would in Linear or GitHub Projects, is something people expect from any project tool. On a normal platform that’s just a feature on a server. On Radicle there is no server, so the question becomes: where does a board live, and who’s allowed to move things on it?
The answer is that a board is its own collaborative object, stored in the repository and replicated peer to peer like issues and patches are. It only stores where each card sits. Titles, labels, state and assignees all come from the issues and patches themselves, so a board can never drift out of sync with the work it shows.
Cards that reflect the real work
- Every issue and patch shows up on the board without anyone adding it. Cards nobody has placed yet are ordered by priority, then age.
- Cards show what you need at a glance: priority, labels, who it’s assigned to, and how old it is.
- A patch that mentions an issue shows up on that issue’s card. An open patch moves the issue to In Review, and a merged patch closes it.
- Repositories can share cards from other public repositories, so a board can cover a project that spans several repos.
Columns that mean something
A column isn’t just a label. Each one either keeps issues open, closes them as solved, or closes them as something else. Dragging an issue into Done actually closes the issue, for everyone, not just on your board. A repo can have several boards, but the app doesn’t mention that until someone makes a second one.
Who can move a card
This was the interesting design problem. With no server, there’s no admin panel deciding permissions. Every peer has to reach the same verdict on its own about whether a move was allowed. The rule we landed on: maintainers can move anything, the author of an issue or patch can move their own card, and anyone assigned to an issue can move it too. An assignee’s move even points back to the moment a maintainer assigned them, as proof every peer can check. Moves that aren’t allowed are simply ignored.
In the interface, that means a card you can’t move doesn’t pretend you can. Pick it up and it stretches a little, shows a lock, and settles back where it was. Press Escape mid-drag and the card goes home too.
A universal navigator
People share Radicle links everywhere: chat, email, issues, the
terminal. Those links come in a lot of shapes. There are explorer
URLs on any number of different seeds, rad:// URIs, and
bare IDs. Getting from one of those to the right place in the app
meant clicking through by hand. So I built one place to go
anywhere: press Cmd+K.
Paste anything
Paste any Radicle link or ID and the navigator takes you straight to the repo, issue, patch or commit it points at. It doesn’t matter which seed or explorer the link came from. If it points to something that isn’t on your machine yet, it tells you instead of leaving you on a blank page.
Search everything
Type instead, and it searches the titles and IDs of every issue and patch across every repo on your machine, with open ones first. With nothing typed, it lists your repos in the same order as the sidebar, pinned ones first, so the shortcut is also the fastest way to switch repos.