A usability study of radicle
Some months ago I (again) came across radicle. It was released in version 1.0.0 in 2024 and I wanted to try it since, but as you know, life happens and always gets in the way of plans. But I found some time this year to try it out and even contribute to its codebase (of course dogfooding and using radicle itself to contribute to it). Unfortunately, my progress contributing declined since, but that's due to the fact that radicle inspired me to start working on my distributed social network idea again, so I consider that actually a win rather than a loss. ;–)
Still, I wanted to get some contributions out again, and as you should know, contributions to (FLOSS) software projects does not need to happen in code, but can also happen in documentation, translation, or in spreading the word. This here is my attempt to contribute to radicle in a way that benefits the project: In writing up where it must improve its UX to succeed from my point of view.
Of course this is going to be a very subjective article. Some bits are even ranty. I am writing all of this in good faith and with best intentions! I want radicle to succeed!
Still, I have to say...
...where I am coming from
I want to make clear where I am coming from. As this is going to be highly subjective, I think it must be totally clear where my current position is, to be able to understand why I am thinking a certain way about the topic at hand.
So here we go with some things that I think are important:
- I know git. No, I don't mean I can commit and push, I know git very good. I can work with email patchsets, I rebase a lot, including rewriting patches in the middle of a rebase for example. Most people fear
git-rebase, I don't. - I have strong opinions (1, 2, 3, 4) on how to work in a distributed manner with git. That reminds me that I should also write up an article “I hate linear git histories” or something like that. I think this is very important for reviewing the UX of radicle. We'll see. FWIW, I think about open-sourcing my aforementioned distributed social network idea at some point, but only making it available via a
git cloneable git URL and only taking email patches as a first step, especially to making it only “contributable” to a certain audience. And then only later making it available via code forges like github, codeberg and sourcehut and taking PRs from all of them at once (not tying myself to one particular infrastructure provider there). You probably noticed that radicle is not in the list above. You may find out why in this very article. - FWIW, I hate when people put their VS Code configuration in the repository. It simply does not belong there, keep your stuff locally! If I would notice radicle altering my repository, even just putting a dotfile there, that would be a deal-braker for me!
- I am working on the CLI exclusively. I feel like this has to be stated. When I see students at my local hackerspace I see a lot of stuff like IntelliJ, VS Code, stuff like that. I don't mind that, but I feel like sometimes these tools hide things that ought to be known before you abstract them away. Maybe another blog post lurking here? Anyways, I dig into stuff on the CLI. For example, I don't use any fancy git UI either (think gitui). These tools might be awesome, I am used to the plain
gitcommand. I rarely commit from within my editor (neovim) even. I usetig, but only for visualizing the log graph (I even have agit-graphutility which does the same thing, but muscle memory keeps me usingtig). This also has the “downside” that I cannot comment on interaction between these tools and radicle – I simply don't know howgitui, IntelliJ or VS Code would interact with a repository that is on the radicle network. Would they even? Would there be a noticable difference? I simply don't know.
I hope the above points set the theme a bit.
How this is structured
I thought a lot about how to structure this article so that the radicle maintainers get the most value out of it. I think the best idea is that I simply write it from the standpoint of a person that wants to try it out. So I am going to install radicle for the first time here (on a different device than I did before, so it is really a fresh install) and I will write down my thoughts and questions in the process of doing so.
I try to summarize my questions and ideas, even the very minor (or even invalid) ones.
I hope that the radicle maintainers can distill them into “TODOs” for their implementation.
That being said, I am not a “default linux user” which does some apt install thingy and then stuff is there in general. I installed radicle on my NixOS hosts with home-manager and also installed the systemd services provided by home-manager (or nixos/nixpkgs).
Goal
My goal for this setup is as follows:
I have two devices, each of them has a copy of the project. That project is proprietary and not for public release yet. Though as I want to release it as GPL software at some point, there is not much damage done if I mess this up and accidentially expose it to the internets. You will see later why I explicitely state this. Currently I sync via codeberg, pushing on one host, fetching/pulling on the other.
As both devices are not online at the same time (most of the time), as these are both personal computers, I know that I need some always-online instance that syncs the repository for me.
For that I want to install radicle as a seeder on my home-server as well (a small device that sits in my office, not a big rack or something). I want that device only to sync that one repository and everything that belongs to it. You'll see later why I state this explicitely.
Installation
So, for the installation I went the (for me) normal way of services.radicle.enable (for that home server) and the equivalent in home-manager terms for the personal computers.
Here's the first gotcha though: I had to programs.radicle.settings.node.alias = "matthiasbeyer"; in my home-assistant configuration, which wasn't obvious for me. This generates a config.json file in my $HOME, as I would expect. But I didn't think that I had to rad auth to generate some public-private keypair and that this process would try to write that config file.
So, here's the first todo for the maintainers: If I rad auth and that fails because it cannot write $HOME/.radicle/config.json, don't just tell me that this failed, but also what would have been done. Why did you want to write this file?
I continued, but I am not sure whether this might be the root of all further issues because there's now something missing from that configuration file.
After the installation I had to rad node start, although I enabled the relevant systemd services in my nixos / home-manager configuration. There's also a shutdown setting, which tells the node to shut down after (for me) 5 minutes of idle time. That did not work at all, the nodes (on both personal computers) are running after rad node start + password, and only get stopped via rad node stop.
Setting up the repository
So then I cded to my project and rad inited it. I was queried for a name for the repository and whether it would be a public or private one. I went with the latter.
Later I noticed that there is also some “Repository follow scope”. To this day I am not sure what that even is.
On my second personal computer I did the same. Fuck. Apparently, there is some rad init --existing which would have associated that repository with the already-initialized on my other host. Who would have guessed?
So after finally getting everything to sync (see below) I rad cloned my own repository from my home server (although I already have it, but that one is initialized and I don't know how to un-initialize it...).
For some commands, it still tells me that “there is no fork”, ... and I see in rad help that there is a rad fork command, but I don't even understand what that's supposed to mean,...
Syncing
Getting the repository to sync to my home server was a big BIG issue. I did not fully understand how I made it sync. I executed rad-system commands on that homeserver almost blindly.
Maybe it was some rad-system seed repository_hash something something. I don't know.
What I would have liked is a command that I can execute where I initialized the repository that is something along the lines of rad sync --to <node id of homeserver> and that then tells me “Hey, that remote instance does not accept this repository, here's what you need to do to make it: ...command...command...command”.
But somehow I managed to get it to sync to that homeserver of mine. I am not sure though, whether future changes will be synced there as well.
I am also not sure whether my home server now syncs that repository to the world. There's something called “scope”, which I set to “followed”. I also rad followed my personal computers on that home server and on each home server I rad followed the other personal computer and the home server (and added aliases for all).
- I am not sure what “follow”ing even means in this case
- I am pretty sure that it doesn't help at all if both personal computers are online, because I suspect they cannot connect to eachother (ports not open? Not sure...)
- (nitpick) I don't know what these aliases are exactly – I suspect that these are only human-readable names... but I am completely unsure whether they are synced to the network in any form?
Node status
The next thing I do not understand and find suspicious is, if I execute rad node status, I get a list of connected peers. But why? I don't want to connect to all of them, I want to connect to my home server and, if it is online, to my other personal computer.
The same is true for my home server – why is it connect to some hosts I do not want it to connect to? That makes me suspicious whether my repository gets synced to these, and I don't want that.
For a service that I run on my server that provides a radicle node, I would like it to be closed down to the max at first, with the node operator having explicitely to allow connections. There's nothing wrong with a “no connection at all by default” and I can configure it to “allow all connections”, but the default for a server software should be restricted.
I see that “low friction to get running” is a valid excuse here, but IMO not for the server side of things. Having an “open all gates by default” is exactly the opposite of what I would expect!
Delegates?
I already understood the concept of “delegates”, I suppose – in my setup, I want to enable my second personal computer to push to my master branch, too – not one of my PCs is the single source of truth for my master branch, but both of them are.
What I do not yet understand is how conflicts would be handled here – if my two devices are air-gapped and I update and push master on both of them, that would mean that either would have to merge master from the other once they sync. That much is clear to me.
But then there is this idea of “canonical branches” that is floating around... which solves this? Or doesn't? I don't know. I don't see docs on this.
Also: Does the home server have to be a delegate? I suppose not, but I am not sure... FWIW I added it and now am not able to remove it again. Why? Well... the error message is not clear! It says I must follow some other peer (the one of the second personal computer) to remove the delegate for the home server. Why? I don't know.
Maybe, just maybe the rad follow aliases get mixed up at some point? Because after some careful investigation the aliases did not match the key I was expecting... but from my shell history I can tell that they were added correctly. Something to look at definitively!
git-notes
This is maybe a bit obscure, but nonetheless...
I do keep git-notes in my repository. What I do is generate some metadata about the project for every other merge to my master branch, such as test count, binary size, etc.
Right now I do not see how I could sync them via radicle. I can do git push rad refs/notes/commits, and that works, but when trying to fetch these on the other personal computer (git fetch rad refs/notes/commits), it just does nothing from what I see. No output, so no notes fetched.
Working with a repository
For the following, I had to use a new repository, because mine obviously does not yet have any issues, pull requests or discussions attached to it, and randomly putting some in there does not nearly result in the same experience.
So I went to get the radicle repository itself (“heartwood”) and play with that for a bit. To find it, I opened radicle-desktop, and used the search there (which redirects to https://search.radicle.xyz – which is nice).
After cloning the repository (which apparently works with rad checkout rather than rad clone, at least that's what radicle-desktop suggests), I am greeted with a load of remotes that I did not expect:
(Redacted for brevity)
$ git remote -v at 13:46:24
fintohaps@z6M... rad://z3.../z6M... (fetch)
fintohaps@z6M... rad://z3.../z6M... (push)
lorenz@z6M... rad://z3.../z6M... (fetch)
lorenz@z6M... rad://z3.../z6M... (push)
rad rad://z3.. (fetch)
rad rad://z3../z6M.. (push)
z6Mkg... rad://z3gq.../z6MkgF... (fetch)
z6Mkg... rad://z3gq.../z6MkgF... (push)
z6Mks... rad://z3gq.../z6MksF... (fetch)
z6Mks... rad://z3gq.../z6MksF... (push)
z6Mkt... rad://z3gq.../z6Mkta... (fetch)
z6Mkt... rad://z3gq.../z6Mkta... (push)
But why? It does not explain this during the clone (or rather checkout) step!
Executing tig now results in a lot of clutter in my tig log panel that I have to visually parse to understand what is going on.
Especially because there are remotes with .../patches/... sprinkled in between that I did not ever expect. Why are there (apparently) PRs that have been merged (apparently heartwood uses fast-forward or rebase merges *grunt*).
Why are these things put into my repository? I did not request them, there is no added value from these refs. If I want to look at them, I can get them from my locally running node, but having them in the repository from the get-go is just clutter I have to mentally cope with. Not nice.
Okay, well... now lets have a look at their current issues.
For that, I executed rad issue list. I was greeted with a nicely formatted table, which exceeded my screen though, so I had to scroll up to find the column names. Not nice.
If we now look further, to check whether there are also pull-requests (confusingly named “patches”) for that repository, we execute rad patch.
Luckily, this does not exceed my screen! That table looks rather good, but there's one column missing: How many comments were posted on a particular patch. Because I really wanted to look at a patch with lots of discussion (and reviews?), so see how that is displayed by the rad command.
The table has a column “Reviews”, but I don't know what its values mean... which are - - - - - ... umm what?
If I rad patch list --merged it prints all the things (which even on my beefy machine takes almost 2 seconds), which is not helpful either.
Unfortunately, I was not able to find a pull-request with discussions on it. Not even after using radicle-desktop to click through the list of open and closed (merged) pull-requests. This leaves the bad taste that even radicle developers rather use their zulipchat instance to talk about patchsets rather than the tool. Dogfooding anyone?
Edit: Apparently I missed – or failed to properly click on the button – to check the already-merged PRs, there's plenty with discussions on them. So this one is clearly on me.
Okay, so back to rad issue. I found an interesting issue via radicle-desktop, which is about “SystemD” (not to confuse with “systemd” of course).
I found that patch via the desktop interface,... I could now find that hash that is the ID of that issue and copy that to the CLI to open the issue there... but that's too complicated, lets just...
rad issue list | grep SystemD
...nothing. Huh? Okay, lets try
rad issue list | grep -i systemd
...nothing? Whut? ... okay
rad issue list --all | grep -i systemd
aaah there it is. It is closed! Okay, well then rad issue list --closed | grep -i systemd should clearly yield something, right? But it doesn't? Whut? But rad issue list --solved | grep -i systemd does ... although when looking at the exact issue rad issue show 5767d6f it says... Status Closed (solved). MEH! Okay, so a solved issue is not a closed one, although it is named “closed”.
The rad issue show output for a specific issue also needs a bit of work UI wise: for example there's the header for each message, which is not really visually seperated from the content of the message. Small issue, but well...
For that last bit, I figured I could submit a pull request to heartwood to get that fixed. So I dove into the code, found the line where the output gets constructed and added a sweet line of whitespace in there. Easy. Now we have something that we can PR against the upstream project, a branch where I committed that change and of course tested the change, by running it with the same issue from above.
Now... how do I submit that patch as a PR?
I looked for rad patch propose or rad patch publish... (which should rather be rad pull-request or something along those lines).
But I couldn't find it.
After consulting the documentation on https://radicle.xyz, I found that I have to push that patch to my special remote “rad” and to HEAD:refs/patches – Whut? There's absolutely no reason why this has to be so complicated. Why is there a command for everything in here, but not for the most common thing ever?
And once I do that push, my editor opens for the pull request message??? (I am sure it cannot use git-notes or branch descriptions, right?).
That is the weirdest workflow of all so far, to be honest. What I would expect is a rad request-pull with some flags for fetching the patch description from git-branch description or git-notes, plus some flag for editing or a simple --message/-m and so on.
Also, that the editor is now pre-filled with the commit message of that commit is weird! I suspect that if there are multiple commits on that branch, they all get put into that PR message? That would be even more weird, to be honest! What I would expect is an empty text/markdown document. Possibly some html comments that tell me what goes in here. Possibly a diffstat, that does not get put into the final PR message, but not this!
After editing my PR message, radicle seems to sync that to the network, so lets find it in the official upstream via the web interface! And yes, there it is: https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/patches/e1f7101a364166ba14fd9610e75aab312d635b5f
Nice!
The radicle output also tells me what to do to update this patch... and of course it wants me to git push if I simply have more commits, or to force-push to some weird remote: git push rad --force-with-lease HEAD:patches/e1f7101a364166ba14fd9610e75aab312d635b5f. How about rad patch update --to HEAD with --no-edit if I do not want to comment that new update?
Another thing that I found was: If I now run rad issue list, that pull request is opened by matthiasbeyer-hikari. Where does that come from? I've set that name at some point, but when I run rad self, my alias is matthiasbeyer, which I would have expected to be visible in the pull request as well (FWIW, the web interface shows the correct alias, without the -hikari)!
TUI
The rad-tui is a really nice project (which IMO should be part of the radicle repository)!
It still has some rough edges:
rad tui issue list 5767d6fhas the exact same output asrad issue list 5767d6f– where exactly is that sweet tui?- (nitpick) It opens with “preview” enabled by default, which is exactly the opposite of what I would have expected. Opening it with
rad tui issueshould give me a list of issues, not one pre-selected and its details showing. - There's no “reply” binding apparently? Weird!
- It does not do code highlighting for markdown code blocks
General UX
Some things I found while playing around with the stuff I described above which did not get explicitely mentioned yet:
- Naming pull-requests “patches” is ... no I won't use swearwords here.
- Finding the node ID (NID) is really not nice:
rad node statusand then find the NID in that blob of output. Why notrad node idand just get that ID?
Recap
All of the above seems pretty negative. It isn't. Let me repeat: I want radicle to succeed. I love the idea, I love the project. I think it is on the right track and I think it might be the solution to centralized forges. And, compared to federation in forgejo it already exists, which is huge!
All of the above was written with the intention that the project can derive tasks from it. All of the above things are UI and UX things, nothing of this is technical! I hope we can get into a discussion to get the best out of what I (finally) managed to write down and improve radicle with it.
After posting above article, I came to the realization that it sounds way more aggressive than I would have liked it to be. The frustration and anger that shines through above lines might have left a bitter taste with some of my readers here and I want to apologize for that. My intention was not to hurt anyone. radicle is an awesome piece of software with (in my opinion) a lot of raw edges. A diamond that needs proper polish, so to speak. I hope we can polish that diamond together!