It’s been a while, so here’s an update about Aardvark, our initiative to bring local-first collaboration to GNOME apps!
A quick recap of what happened since my last update:
Since December, we had three more Aardvark-focused events in Berlin
We discussed peer-to-peer threat models and put together designs addressing some of the concerns that came out of those discussions
We switched from using Automerge to Loro as a CRDT library in the app, mainly because of better documentation and native support for undo/redo
As part of a p2panda NLnet grant, Julian Sparber has been building the Aardvark prototype out into a more fully-fledged app
We submitted and got approved for a new Prototypefund grant to further build on this work, which started a few weeks ago!
With the initiative becoming more concrete we retired the “Aardvark” codename, and gave the app a real GNOME-style app name: “Reflection”
The Current State
As of this week, the Reflection (formerly Aardvark) app already works for simple Hedgedoc-style use cases. It’s definitely still alpha-quality, but we already use it internally for our team meetings. If you’re feeling adventurous you can clone the repo and run it from Builder, it should mostly work :)
Our current focus is on reliability for basic collaboration use cases, i.e. making sure we’re not losing people’s data, handling various networking edge cases smoothly, and so on. After that there are a few more missing UI features we want to add to make it comfortable to use as a Hedgedoc replacement (e.g. displaying other people’s cursors and undo/redo).
At the same time, the p2panda team (Andreas, Sam, and glyph) are working on new features in p2panda to enable functionality we want to integrate later on, particularly end-to-end encryption and an authentication/permission system.
Prototype Fund Roadmap
We have two primary goals for the Prototype Fund project: We want to build an app that’s polished enough to use as a daily driver for meeting notes in the near-term future, but with an explicit focus on full-stack testing of p2panda in a real-world native desktop app. This is because our second goal is kickstarting a larger ecosystem of local-first GNOME apps. To help with this, the idea is for Reflection to also serve as an example of a GTK app with local-first collaboration that others can copy code and UI patterns from. We’re not sure yet how much these two goals (peer-to-peer example vs. daily driver notes app) will be in conflict, but we hope it won’t be too bad in practice. If in doubt we’ll probably be biased towards the former, because we see this app primarily as a step towards a larger ecosystem of local-first apps.
To that end it’s very important to us to involve the wider community of GNOME app developers. We’re planning to write more regular blog posts about various aspects of our work, and of course we’re always available for questions if anyone wants to start playing with this in their own apps. We’re also planning to create GObject bindings so people can easily use p2panda from C, Python, Javascript, Vala, etc. rather than only from Rust.
Designs for various states of the connection popover
We aim to release a first basic version of the app to Flathub around August, and then we’ll spend the rest of the Prototype Fund period (until end of November) adding more advanced features, such as end-to-end encryption and permission management. Depending on how smoothly this goes, we’d also like to get into some fancier UI features (such as comments and suggested edits), but it’s hard to say at this point.
If we’re approved for Prototype Fund’s Second Stage (will be announced in October), we’ll get to spend a few more months doing mostly non-technical tasks for the project, such as writing more developer documentation, and organizing a GTK+Local-First conference next spring.
Meet us at GUADEC
Most of the Reflection team (Julian Sparber, Andreas Dzialocha, and myself) are going to be at GUADEC in July, and we’ll have a dedicated Local-First BoF (ideally on Monday July 28th, but not confirmed yet). This will be a great opportunity for discussions towards a potential system sync service, to give feedback on APIs if you’ve already tried playing with them, or to tell us what you’d need to make your app collaborative!
Two weeks ago we got together in Berlin for another (Un)boiling The Ocean event (slight name change because Mastodon does not deal well with metaphors). This time it was laser-focused on local-first sync, i.e. software that can move seamlessly between real-time collaboration when there’s a network connection, and working offline when there is no connection.
The New p2panda
This event was the next step in our ongoing collaboration with the p2panda project. p2panda provides building blocks for local-first software and is spearheaded by Andreas Dzialocha and Sam Andreae. Since our initial discussions in late 2023 they made a number of structural changes to p2panda, making it more modular and easier to use for cases like ours, i.e. native GNOME apps.
Sam and Andreas introducing the new p2panda release.
This new version of p2panda shipped a few weeks ago, in the form of a dozen separate Rust crates, along with a new website and new documentation.
On Saturday night we had a little Xmas-themed release party for the new p2panda version, with food, Glühwein, and two talks from Eileen Wagner (on peer-to-peer UX patterns) and Sarah Grant (on radio communication).
The Hackfest
Earlier on Saturday and then all day Sunday we had a very focused and productive hackfest to finally put all the pieces together and build our long-planned prototype codenamed “Aardvark”, a local-first collaborative text editor using the p2panda stack.
Simplified diagram of the overall architecture, with the GTK frontend, Automerge for CRDTs, and p2panda for networking.
Our goal was to put together a simple Rust GTK starter project with a TextView, read/write the TextView’s content in and out of an Automerge CRDT, and sync it with other local peers via p2panda running in a separate thread. Long story short: we pulled it off! By the end of the hackfest we had basic collaborative editing working on the local network (modulo some bugs across the stack). It’s of course still a long road from there to an actual releasable app, but it was a great start.
The reason why we went with a text editor is not because it’s the easiest thing to do — freeform text is actually one of the more difficult types of CRDT. However, we felt that in order to get momentum around this project it needs to be something that we ourselves will actually use every day. Hence, the concrete use case we wanted to target was replacing Hedgedoc for taking notes at meetings (particularly useful when having meetings at offline, where there’s no internet).
The current state of Aardvark: Half of the UI isn’t hooked up to anything yet, and it only sort of works on the local network :)
While the Berlin gang was hacking on the text editor, we also had Ada, a remote participant, looking into what it would take to do collaborative sketching in Rnote. This work is still in the investigation stage, but we’re hopeful that it will get somewhere as a second experiment with this stack.
Thanks to everyone who attended the hackfest, in particular Andreas for doing most of the organizing, and Sam Andreae and Sebastian Wick, who came to Berlin specifically for the event! Thanks also to Weise7 for hosting us, and offline for hosting the release party.
The Long Game
Since it’s early days for all of this stuff, we feel that it’s currently best to experiment with this technology in the context of a specific vertically integrated app. This makes it easy to iterate across the entire stack while learning how best to fit together various pieces.
However, we’re hoping that eventually we’ll settle on a standard architecture that will work for many types of apps, at which point parts of this could be split out into a system service of some kind. We could then perhaps also have standard APIs for signaling servers (sometimes needed for peers to find each other) and “dumb pipe” sync/caching servers that only move around encrypted packets (needed in case none of the other peers are online). With this there could be many different interchangeable sync server providers, making app development fully independent of any specific provider.
Martin Kleppmann’s talk at Local-First Conf 2024 outlines his vision for an ecosystem of local-first apps which all use the same standard sync protocol and can thus share sync services, or sync peer-to-peer.
This is all still pretty far out, but we imagine a world where as an app developer the only thing you need to do to build real-time collaboration is to integrate a CRDT for your data, and use the standard system API for the sync service to find peers and send/receive data.
With this in place it should be (almost) as easy to build apps with seamless local-first collaboration as it is to build apps using only the local file system.
Next Steps
It’s still early days for Aardvark, but so far everyone’s very excited about it and development has been going strong since the hackfest. We’re hoping to keep this momentum going into next year, and build the app into a more full-fledged Hedgedoc replacement as part of p2panda’s NGI project by next summer.
That said, we see the main value of this project not in the app itself, but rather the possibility for our community to experiment with local-first patterns, in order to create capacity to do this in more apps across our ecosystem. As part of that effort we’re also interested in working with other app developers on integration in their apps, making bindings for other languages, and working on shared UI patterns for common local-first user flows such as adding peers, showing network status, etc.
If you’d like to get involved, e.g. by contributing to Aardvark, or trying local-first sync in your own app using this stack feel free to reach out on Matrix (aardvark:gnome.org), or the Aardvark repo on Github.
Last weekend we had another edition of last year’s post-All Systems Go hackfest in Berlin. This year it was even more of a collaborative event with friends from other communities, particularly postmarketOS. Topics included GNOME OS, postmarketOS, systemd, Android app support, hardware enablement, app design, local-first sync, and many other exciting things.
This left us with an awkward branding question, since we didn’t want to name the event after one specific community or project. Initially we had a very long and unpronounceable acronym (LMGOSRP), but I couldn’t bring myself to use that on the announcement post so I went with something a bit more digestible :)
“Boiling The Ocean” refers to the fact that this is what all the hackfest topics share in common: They’re all very difficult long-term efforts that we expect to still be working on for years before they fully bear fruit. A second, mostly incidental, connotation is that the the ocean (and wider biosphere) are currently being boiled thanks to the climate crisis, and that much of our work has a degrowth or resilience angle (e.g. running on older devices or local-first).
I’m not going to try to summarize all the work done at the event since there were many different parallel tracks, many of which I didn’t participate in. Here’s a quick summary of a few of the things I was tangentially involved in, hopefully others will do their own write-ups about what they were up to.
Mobile
Mainline Linux on ex-Android phones was a big topic, since there were many relevant actors from this space present. This includes the postmarketOS crew, Robert with his camera work, and Jonas and Caleb who are still working on Android app support via Alien Dalvik.
To me, one of the most exciting things here is that we’re seeing more well-supported Qualcomm devices (in addition to everyone’s favorite, the Oneplus 6) these days thanks to all the work being done by Caleb and others on that stack. Between this, the progress on cameras, and the Android app support maybe we can finally do the week-long daily driving challenge we’ve wanted to do for a while at GUADEC 2025 :)
Design
On Thursday night we already did a bit of pre-event hacking at a cafe, and I had an impromptu design session with Luca about eSIM support. He has an app for this at the moment, though of course ideally this should just be in Settings longer-term. For now we discussed how to clean up the UI a bit and bring it more in line with the HIG, and I’ll push some updates to the cellular settings mockups based on this soon.
On Friday I looked into a few Papers things with Pablo, in particular highlights/annotations. I pushed the new mockups, including a new way to edit annotations. It’s very exciting to see how energetic the Papers team is, huge kudos to Pablo, Qiu, Markus, et al for revitalizing this app <3
On Saturday I sat down with fellow GNOME design contributor Philipp, and looked at a few design questions in Decibels and Calendar. One of my main takeaways is that we should take a fresh look at the adaptive Calendar layout now that we have Adwaita breakpoints and multi-layout.
47 Release Party
On Saturday night we had the GNOME 47 release party, featuring a GNOME trivia quiz. Thanks to Ondrej for preparing it, and congrats to the winners: Adrian, Marvin, and Stefan :)
Local-First
Adrian and Andreas from p2panda had some productive discussions about a longer-term plan for a local-first sync system, and immediate next steps in that direction.
We have a first collaboration planned in the form of a Hedgedoc-style local-first syncing pad, codenamed “Aardvark” (initial mockups). This will be based on a new, more modular version of p2panda (still WIP, but to be released later this year). Longer-term the idea is to have some kind of shared system level daemon so multiple apps can use the same syncing infrastructure, but for now we want to test this architecture in a self-contained app since it’s much easier to iterate on. There’s no clear timeline for this yet, but we’re aiming to start this work around the end of the year.
GNOME OS
On Sunday we had a GNOME OS planning meeting with Adrian, Abderrahim, and the rest of the GNOME OS team (remote). The notes are here if you’re interested in the details, but the upshot is that the transition to the next-generation stack using systemd sysupdate and homed is progressing nicely (thanks to the work Adrian and Codethink have been doing for our Sovereign Tech Fund project).
If all goes to plan we’ll complete both of these this cycle, making GNOME OS 48 next spring a real game changer in terms of security and reliability.
Community
Despite the very last minute announcement and some logistical back and forth the event worked out beautifully, and we had over 20 people joining across the various days. In addition to the usual suspects I was happy to meet some newcomers, including from outside Berlin and outside the typical desktop crowd. Thanks for joining everyone!
Thanks also to Caleb and Zeeshan for helping with organization, and the venues we had hosting us across the various days:
This week we had a local-first workshop at offline in Berlin, co-organized with the p2panda project. As I’ve written about before, some of us have been exploring local-first approaches as a way to sync data between devices, while also working great offline.
We had a hackfest on the topic in September, where we mapped out the problem space and discussed different potential architectures and approaches. We also realized that while there are mature solutions for the actual data syncing part with CRDT libraries like Automerge, the network and discovery part is more difficult than we thought.
Network Woes
The issues we need to address at the network level are the classic problems any distributed system has, including:
Discovering other peers
Connecting to the other peers behind a NAT
Encryption and authentication
Replication (which clients need what data?)
We had sketched out a theoretical architecture for first experiments at the last hackfest, using WebRTC data channel to send data, and hardcoding a public STUN server for rendezvous.
A few weeks after that I met Andreas from p2panda at an event in Berlin. He mentioned that in p2panda they have robust networking already, including mDNS discovery on the local network, remote peer discovery using rendezvous servers, p2p connections via UDP holepunching or relays, data replication, etc. Since we’re very interested in getting a low-fi prototype working sooner rather than later it seemed like a promising direction to explore.
p2panda
The p2panda project aims to provide a batteries-included SDK for easy local-first app development, including all the hard networking stuff mentioned above. It’s been around since about 2020, and is currently primarily developed by Andreas Dzialocha and Sam Andreae.
The architecture consist of nodes and clients. Nodes include networking, materialization, and an SQL database. Clients sign and create data, and interact with the node using a GraphQL API.
As of the latest release there’s TLS transport encryption between nodes, but end-to-end data-encryption using MLS is still being worked on, as well as a capabilities system and privacy-respecting deletion. Currently there’s a single key/value CRDT being used for all data, with no high-level way for apps to customize this.
The Workshop
The idea for the workshop was to bring together people from the GNOME and local-first communities, discuss the problem space, and do some initial prototyping.
For the latter Andreas prepared a little bookmark manager demo project (git repository) that people can open in Workbench and hack on easily. This demo runs a node in the background and accesses the database via GraphQL from a simple GTK frontend, written in Rust. The demo app automatically finds other peers on the local network and syncs the data between them.
We had about 10 workshop participants with diverse backgrounds, including an SSB developer, a Mutter developer, and some people completely new to both local-first and GTK development. We didn’t get a ton of hacking done due to time constraints (we had enough program for an all-day workshop realistcally :D), but a few people did start projects they plan to pursue after the workshop, including C/GObject bindings for p2panda-rs and an app/demo to sync a list of map locations. We also had some really good discussions on local-first architecture, and the GNOME perspective on this.
Thoughts on Local-First Architectures
The way p2panda splits responsibilities between components is optimized for simple client development, and being able to use it in the browser using the GraphQL API. All of the heavy lifting is done in the node, including networking, data storage, and CRDTs. It currently only supports one CRDT, which is optimized for database-style apps with lots of discrete fields.
One of the main takeaways from our previous hackfest was that data storage and CRDTs should ideally be in the client. Different apps need different CRDTs, because these encode the semantics of the data. For example, a text editor would need a custom text CRDT rather than the current p2panda one.
Longer-term we’ll probably want an architecture where clients have more control over their data to allow for more complex apps and diverse use cases. p2panda can provide these building blocks (generic reducer logic, storage providers, networking stack, etc.) but these APIs still need to be exposed for more flexibility. How exactly this could be done and if/how parts of the stack could be shared needs more exploration :)
Theoretical future architectures aside, p2panda is a great option for local-first prototypes that work today. We’re very excited to start playing with some real apps using it, and actually shipping them in a way that people can try.
What’s Next?
There’s a clear path towards first prototype GNOME apps using p2panda for sync. However, there are two constraints to keep in mind when considering ideas for this:
Data is not encrypted end-to-end for now (so personal data is tricky)
The default p2panda CRDT is optimized for key / value maps (more complex ones would need to be added manually)
This means that unfortunately you can’t just plug this into a GtkSourceView and have a Hedgedoc replacement. That said, there’s still lots of cool stuff you can do within these constraints, especially if you get creative in the client around how you use/access data. If you’re familiar with Rust, the Workbench demo Andreas made is a great starting point for app experiments.
Some examples of use cases that could be well-suited, because the data is structured, but not very sensitive:
Thanks to Andreas Dzialocha for co-organizing the event and providing the venue, Sebastian Wick for co-writing this blog post, Sonny Piers for his help with Workbench, and everyone who joined the event. See you next time!
In celebration of the 45 release we had a hackfest and release party in Berlin last week. It was initially supposed to be a small event, but it turns out the German community is growing more rapidly than we thought! In the end we were around 25 people, about half of them locals from Berlin :)
GNOME OS
Since many of the GNOME OS developers were in town for All Systems Go, this was one of the main topics. In addition to Valentin, Javier, and Jordan (remote), we also had Lennart from systemd and Adrian from carbonOS and discussed many of the key issues for image-based operating systems.
I was only present for part of these discussions so I’ll leave it to others to report the results in detail. It’s very exciting how things are maturing in this area though, as everyone is standardizing on systemd’s tools for image-based OSes.
Discussing the developer story on image-based OSes with Jonas and Sebastian from GNOME Shell/mutter. Left side: Adrian, Javier, Valentin, Sebastian. Right side: Jonas, Kai, Lennart
Local-First
On Saturday the primary topic was local-first. This is the idea that software should always work offline, and optionally use the network when available for device sync and collaboration. This allows for people to own their data, but still have access to modern features like multiplayer editing.
People in the GNOME community have long been interested in local-first and we’ve had various discussions and experiments in this direction over the past few years. However, so far we have not really investigated how we’d implement it at a larger scale, and what concrete steps in that direction would look like.
For context, any sync system (local-first or not) needs the following things:
Network: Device discovery, channel to send the actual data, way to handle offline nodes, encryption, device authentication, account management
Sync: Merging data from different peers, handling conflicts
UI: User interface for viewing and manipulating the data, showing sync status, managing devices, permissions, etc.
Local-first usually refers to systems that do the “sync” part on the client, though that doesn’t mean the other areas are easy :)
Muse
Adam Wiggins stopped by on Saturday morning to tell us about his work on Muse, a local-first whiteboard app for Apple platforms. While it’s a totally different tech stack and background, it was super interesting because Muse is one of very few consumer apps using local-first sync in production today.
Some of my takeaways from the session with Adam:
Local-first means all the logic lives in the client. In the Muse architecture, the server is extremely simple, basically just a dumb pipe routing data between clients. While data is not encrypted end-to-end in their case, it’s possible to use this same architecture with E2E.
CRDTs (conflict-free replicated data types) are a magical new advancement in computer science over the past few years, which makes the actual merging of content relatively easy.
Merge conflicts are not as big a deal as one might think, and not the hardest problem to solve in this space.
Local-first is a huge opportunity for desktop projects like GNOME. We were not really able to be competitive with proprietary software in the past decade on features like sync and multiplayer because we can’t realistically run huge cloud services for every single app/use case. Local-first could change this, since the logic is shifting back to the client. Servers become generic dumb pipes, which all kinds of apps can use without needing their own custom sync server.
To learn more about Muse, I recommend watching Adam’s Local-First Meetup talk from earlier this year, which touches on many of the topics we discussed in our hackfest session as well.
Other Relevant Art
The two projects we discussed as relevant art from our community are Christian Hergert’s Bonsai, and Carlos Garnacho’s work on RDF sync in tracker (codename “Emergence”).
Bonsai is not quite local-first architecturally, since it assumes an always-on home server. This server hosts your data and runs services, which apps on your other devices can use to access data, sync, etc. This is quite different from the dumb pipe server model discussed above, and of course comes with the usual caveats with any kind of public-facing service on local networks (NAT, weird network configurations, etc.).
Codename “Emergence” is a way to sync graph databases (such as tracker’s SPARQL database). It only touches on the “sync” layer, and is only intended for app data, e.g. bookmarks, contacts, and the like. There was a lot of discussion at the hackfest about whether the conflict resolution algorithm is/could be a CRDT, but regardless, using this system for syncing some types of content wouldn’t affect the overall architecture. We could use it for syncing e.g. bookmarks, and share the rest of the stack (e.g. network layer) with other apps not using tracker.
By the end of the hackfest, we had a rough consensus that long-term we probably want something like this:
Muse-style architecture with dumb pipe sync servers that only route encrypted traffic between clients
Some kind of system daemon that apps can use to send packets to sync servers, so they don’t all have to run in the background
The ability to fall back to other kinds of transport with full compatibility, e.g. local network or USB keys
A client library that makes it easy to integrate sync into apps, using well-established CRDTs
However, there was also a general feeling that we want to go slowly and explore the space before coming up with over-engineered solutions. To this end, we think the best next step is to try CRDTs in small, self-contained apps. We brainstormed a number of potentially interesting use cases, including:
Alarms: Make extra sure you hear your alarms by having it synced on all devices
Scratchpad: Super simple notepad that’s always in sync across devices
Emoji history: The same recently used emoji on all devices
Podcasts: Sync subscription list, episode playback state, per-episode progress, currently played episode, etc.
Birthday reminders: Simple list of birthdays with reminder notifications that syncs across all devices
For a first minimum viable prototype we discussed ways to cut as many corners as possible, and came up with the following plan:
Use an off-the shelf plain text CRDT to build a syncing scratchpad as a first experiment
To avoid having to deal with servers, do peer-to-peer transfer only and send data via WebRTC data channel
For peer discovery, just hardcode a public WebRTC STUN server in the client
Simple Rust GTK app mostly consisting of a text area, using gstreamer for WebRTC and automerge for CRDTs
Sync only between two devices
We’ll see how this develops, but it’s great to have mapped out the territory, and put together a concrete plan for next steps in this direction. I’m also conscious that we’re a huge community and only a handful of people were present at the hackfest. It’s very likely that these plans will evolve as more people get involved and we get more experience working with the technology.
If you’d like to experiment with this in your own app and have any questions, don’t hesitate to reach out :)
Our beautiful GNOME 45 cake :)
Transparent Panel
Jonas has had an open merge request for a transparent panel for a number of years, and while we’ve tried to get it over the line a few times we never quite managed. Recently Adrian Vovk was interested in giving it another try, so at the hackfest him and Jonas sat down and did some archaeology on Jonas’ old commits, rebased it, got it to work agian, and opened a new merge request.
Adrian and Jonas hard at work rebasing ancient commits
While there are still a few open questions and edge cases, it’s early in the cycle so there’s a real chance that we might finally get this in for 46 :)
And More!
A few other things I was involved with during the hackfest:
Julian looked into one of my pet bugs: The default generated avatars when you create a new account not looking like AdwAvatar, but using an older, uglier implementation. This is surprisingly tricky because GDM/GNOME Shell can’t show GTK widgets, and the exported PNG avatars from libadwaita can only be exported at the size they’re being displayed at (which is smaller than in GDM/GNOME Shell).
We worked a bit on Annotations in Evince with Pablo, and also interviewed Keywan about how they use annotations in the editorial process for their magazine.
DieBahn was officially renamed to Railway, and we discussed next steps for the app and train APIs in general. Railway works for so many providers by accident, because so many of them use the same backend (HAFAS), but it’d be great to have actual open APIs for querying trains from all providers. Perhaps we need a lobbying group to get some EU legislation for this? :)
We discussed a “demo mode”, i.e. an easy way to set up a device with a bunch of nice-looking apps, pre-loaded with nice-looking content. One potential approach we discussed was a script that installs a set of apps, and sets them up with data by pre-filling their .var/app/ directories. The exact process for creating and updating this data would need looking into, but I’m very interested in getting something set up for this, because not having it really our software hard to demo.
Marvin showed me how he uses CLion for C/Vala development, and we discussed what features Builder would need to gain for him to switch from his custom Vala setup in CLion to Builder.
Julian working on fixing the default avatars
Thanks to Sonny for co-organizing, Cultivation Space for hosting us, and the GNOME Foundation for financial sponsorship! See you next time :)