Skip to content

procyon

From side project to Procyon: a look behind

As we are approaching the last quarter of the year, it is normal for me to look back at all that has been done in the last months, evaluate the current state of the project and plan for the future.

Live fast, and eat trash (a.k.a. reuse code)

The Raccoon client for Mastodon and Friendica was started during the summer of 2024, as a spin-off of the Lemmy client, whose development had started approximately a year before. The initial core was assembled in a short time, taking heavy inspiration from the already established app, trying to reuse as much as possible of the existing code.

The strategy was, more or less: take the existing architecture, switch the APIs to be compatible with Mastodon/Friendica instances, rewrite the content rendering engine (since Lemmy posts were Markdown-based while Mastodon posts are HTML-based), distribute it fast and see how it is received by the public.

There were minimal variations in the tech stack, e.g. using Room Multiplatform instead of SQLDelight for local persistence, using Sentry for crash reporting (instead of saving traces on device and rely on manual reports), using Mokkery in commonTest instead of MockK on androidHostTest.

KMP in its infancy: the technical debt

As a result, large portions of technical debt inherited from an era when KMP was immature were still sitting there:

  • localization used Lyricist (instead of the CMP resource system);
  • DI using Koin with classic DSL (despite annotations being available);
  • Voyager navigation (despite Compose Navigation being ported to multiplatform);
  • endpoint calls used Ktorfit (breaking at every KSP update);
  • setting encryption still used deprecated EncryptedSharedPreferences on Android;
  • build configuration was scattered in individual scripts across subprojects, with subsequent duplication and potentially subtle inconsistencies.

Paying it back: modernizing the stack

Starting from the end of 2024 and for most of 2025, besides reaching feature parity with existing clients and fixing bugs, I always allocated a great amount of time to evolve the project and pay off that debt.

At the beginning of 2026, the situation was the following:

  • resources and localization had been ported to the built-in CMP system, but icons still relied on Google's Material Icons set, which has been deprecated in the meantime;
  • DI was ported to Koin with KSP annotations, breaking reproducible builds, so Kodein was introduced as a temporary replacement until a more modern solution became available;
  • the navigation system was ported to Compose Navigation with regular AndroidX ViewModels;
  • network calls were implemented with plain Ktor, no need for Ktorfit adapters;
  • a custom layer for preference encryption on Android was introduced;
  • build logic was centralized in a set of convention plugins, consistently applied throughout all subprojects for configuration.

Breaking changes and the road to 1.0

In the meantime, Gradle 9 and AGP 9.x were released, making the project's very structure and most of the build logic obsolete.

Therefore, a series of tough decisions had to be made in order to keep the project "healthy" and release a stable 1.0.0 version:

  • revamp the project's website and documentation to get a more modern and functional look (using Zensical);
  • update Gradle and AGP, aligning with the now recommended project structure;
  • add the JVM target and introduce desktop (with .deb package published at every stable release);
  • introduce support for tablets and large screens using adaptive layouts;
  • replace Material Icons with Material Symbols.

And that was exactly what I prioritized for version 1.0.0, which was released on June 8th, 2026.

Beyond 1.0: surfing the KMP bleeding edge

After the first stable release, I started working on the areas which still needed improvement, such as:

  • Navigation 3 with the concept of Scene and SceneStrategy offers a much more elegant solution to the problem of adapting navigation and screen layout to the available screen size;
  • a new player entered the DI scene on KMP: Metro, which finally promises to solve all the issues of previous solutions: conciseness, compile-time safety, power and flexibility;
  • a new Preview annotation to be used in common code was introduced, making setup easier and cleaner.

Those were essentially the areas where I have been working in the last months, alongside a lot of code cleanup and keeping an "aggressive" update strategy for Kotlin versions (adopting new features as they emerged, e.g. explicit backing fields or the unused return type checker).

New features were added as well, in the meantime, such as:

  • support for quote posts on Mastodon;
  • content translation with LibreTranslate;
  • implementing cross-instance exploration;
  • adding themes for the reply bar in forum view;
  • improve search and suggestion functionality with the transition to /v2 endpoints.

What now? A look ahead

What can be expected for the immediate future, then? First of all, I finally feel that I have a solid foundation to build upon, thanks to the modernization effort I've engaged in.

For sure, on the feature side, support will be added for new Mastodon 4.6 features, such as user collections.

On the UX side, I'd like to leverage some new features of CMP (e.g. Grid) for better attachment rendering (instead of the existing carousels).

Finally, tech-wise, I am looking forward to the moment when rich errors are introduced in the language.

In the meantime, I still have to adopt other language features already in preview, like name-based destructuring. Or, since androidx.lifecycle:lifecycle-*:2.11.0 (and its multiplatform port) introduced Scoped ViewModels, this will be the occasion to isolate responsibilities and trim some VM which had grown too large.

The Intentional Raccoon: Why I Code the Hard Way

Do you remember the reaction my admin friend got when they published a post about Raccoon's 1.0.0 release on lemmy.world?

Lemmy users will be Lemmy users: radical, aggressive, self-referential. I tried to work "for" them in the past and create a client app, but for my IRL mental sanity I had to take several pauses over time, and now I am considering archiving that project altogether.1

Let alone the attitude, let alone that the OP was accused of being a bot for using a human expression like "finally", the concerns expressed are real and shared by more and more people.

The Reverse Turing Test

As the Internet is being flooded by AI-generated content, some people are becoming paranoid about "AI slop". It seems like they have nothing better to do than Turing-test everything to detect any non-human footprint and, if they do, they start throwing stones, crying about techno-feudalism, spread alarmism about electricity or water consumption, etc.

What made me most sad about this episode is that the person who was accusing didn't spend any time trying to understand who he was talking to and what he was talking about. So, here is my attempt to clarify who I am and what my personal position is on AI usage in OSS development, especially as side-projects are concerned.

A Choice of Freedom

I got a university degree in CS. I've been working in IT for fifteen years at several different companies. I've been experiencing on my own skin what being into the "meat grinder" feels like, long before 2022 when ChatGPT promised to solve all mankind's (and programmers') issues.

At work, I have to deal with unrealistic deadlines (not decided by me), maintain legacy code ( written by people who left the company), adhere to conventions or obey someone else's decisions ( I don't fully share).

Even when, how much and from which provider to use AI is not my decision from 9 to 6, when I sit at my desk in the office.

Side projects have always been an "escape way" to me. When I come home (in evenings and weekends) and open my IDE I have a totally different approach. I am free to experiment, scout new technologies I like, learn things I choose, set my own schedule and priorities without checking the clock, write and organize code according to my personal taste. There's more, usually I solve real problems I experience in my life in first person.

There is something deeply personal in that. It makes me feel the joy of creating something with my commitment, week after week, like growing a tree. It makes me feel a sense of achievement whenever I implement a feature request or fix a bug, which is greater the more difficult it was to get to the solution, like in a videogame (quest - reward).2

To be honest, developing "at my pace" also has a nostalgic bit to it: it reminds me of an era when developing software still had some "craftsmanship" in it, when I was younger and the future still seemed bright.

If I vibe-coded my apps, what would I get? I would give up on all creativity. The "videogame" dynamic would almost be gone. I wouldn't learn anything which enriches me as a person and as a professional.

Assembly vs. Craft

Working in my 9-to-6 job has always felt like being in an assembly line. You clock in, the machine starts grinding, you have to follow the rhythm, there's no questioning. Efficiency (reduce time to save company's money), functionality (adhere to requirements someone else decided), predictability (enforce standards and prioritize repeatability) are key.

But we all remember «Modern Times» by Chaplin, that is alienating. I am not saying that in my Raccoon apps everything is handmade, I use several third-party libraries and I didn't reinvent the wheel for networking, persistence, data (un)marshaling, UI, etc. But I still choose how all the pieces fit together, like assembling an IKEA piece of furniture.

And, I will never insist too much on it, there's value in learning something the hard way, trying things out, disassembling and reassembling elements to study the internals, is not at all a waste of time. Scientists and technicians, we need to experiment, observe and interpret our results to progress. And the struggle is a great teacher in that: remove the struggle, impoverish the learning.

And resorting to shortcuts is self-inflicted damage too. AI may be wiping out the process of writing code, especially at big scale in large companies. But the ability to understand the "why" behind each line of code is not being superseded: you need it to evaluate and review AI generated code.

Conclusion: Pro-Intentionality, not Anti-AI

The fact that Linus Torvalds recently said that AI is clearly a useful tool, that Linux is not an anti-AI project, and that anyone with issues with that should fork it and go their way, has generated a lot of debate (source here).

Yes, that is true, especially in a project like the Linux kernel where efficiency and scale are the only metrics. For industrial usage, AI makes perfectly sense from an economic point of view.

Nonetheless, the Procyon Project feels like a "workshop" to me (not an industrial "factory") and there is space for human creativity.

Like an artisan pours a little bit of themselves in each one of their works, when side projects are approached like a craft (and not a utility) something similar happens. And the "soul" emerges in little weird details, e.g. some play on words in the theme names.

LLMs aim for the "average" result based on their training data, human work aims for specificities. To me, like to many other devs I guess, there are projects which feel like a "sacred space" for manual craft.

Question

Where do you draw the line between using AI for productivity and letting it take over the "fun" part of your development process?


  1. Or, rather, merging the Lemmy client into the Mastodon / Friendica one, since it is already multi-instance. ↩

  2. The more problems I solve, the more experience I gain, which makes me a better professional in my 9-to-6 job, stay tuned for more in a future article. ↩

The Raccoon's Dilemma: Balancing Features, Maintenance, and Sanity

The idea of writing about the balance between features and maintenance in our apps has been lingering in my mind for over a year. But it never felt like the right moment, so I kept putting it off… ironically, somehow like the maintenance tasks I'm writing about.

This procrastination alone says a lot about my way of managing open-source side projects. It's a delicate dance between personal life and professional obligations. Days only have 24 hours, weeks only have 7 days (trust me, I've checked)!

Between family, a full-time job, and trying to be a functional adult in these chaotic times, maintaining apps can feel like a secondary full-time job, just without any paycheck.

One might wonder: Why on earth do I do this? It's a fair question. In today's article, I want to pull back the curtain on why Raccoon exists and why our development roadmap looks the way it does.

Longing for the Wild

I've worked as a developer for more years than I'd like to admit (let's say my early code belongs in a museum, or perhaps a dumpster). Throughout that time, I often struggled to find room to experiment. As a junior, every choice was made for me. As I moved into mid and senior roles, I was often tethered to legacy systems or constrained by "steering committees" which is often corporate-speak for «we don't like change».

I don't blame them: companies avoid risk because a bad tech choice can haunt a team for years. So, I was usually left playing in the safe corners of projects.

Even while bouncing between backend, web, iOS, and Android, something was missing. I missed the thrill of building from the ground up. I wanted the freedom to choose a foundation, evaluate it in the real world, and pull a complete U-turn on failure without causing a financial crisis for my employer.

The Perfect Storm

In 2023 the stars aligned. Kotlin Multiplatform1 transitioned from Beta to Stable(and Compose Multiplatform was in Alpha for iOS and stable elsewhere).

Meanwhile, the "Reddit API Apocalypse" killed off third-party clients, driving a massive migration toward the "threadiverse" (Lemmy, Kbin, Sublinks, Friendica, etc.).

On a personal note, in early 2023 I had finally switched to a job that actually respected my evenings and weekends, no more unpaid overtime! 🎉 🦝 🎉

These factors converged into a single goal: build a Lemmy client for Android and iOS using KMP. A year later, the Friendica client followed.

This was my playground. I could try libraries, break things, and discard what didn't work for my use cases (like Koin or Ktorfit).

So, finally, I had:

  • total tech freedom: I was the lead, the junior, and the steering committee.
  • continuous learning: hands-on experience with the bleeding edge.
  • purpose: contributing something useful to an ecosystem I actually believed in.

The "Why" Behind the Trash

Even without a paycheck, the ROI on these projects is massive. It's professional growth and psychological satisfaction rolled into one. It's a process of hypothesis and experimentation, not unlike academic research, but with more raccoons and fewer lab coats.

This also explains why instead of always going on and pile new features one on top of the other, sometimes the development process seems to go in circles: e.g. switching from a network library to another, rewrite the app navigation system, migrate localization from a framework to another, etc.

But why talk about this right now?

Recently, I hit a milestone: version 1.0.0 of the Mastodon/Friendica client. A local admin wrote an announcement about it (in Italian). Then they ran it through machine translation and posted an English version.

You know, the Fediverse is federated, so this propagated to Lemmy, where naturally critics arrived:

Quote

Why are people upvoting this? If OP used AI to 'make' this post, obviously the app is gonna be slop too.

The "proofs"? The word "finally" appeared in the middle of a list. Apparently, to some, using transition words is a sign of a robotic takeover rather than, you know, being relieved that a some longed-for feature is available to end users.

The Human (and Raccoon) Element

This brings us back to sustainability. When we maintain open-source projects, we aren't just pushing code; we're managing a community and navigating the weirdness of the Internet.

The "AI slop" accusation stung, but it also highlighted the irony: I do this work specifically to keep the human element alive in tech. I build these apps so we have independent, non-corporate ways to communicate and I do so because I love software development and learning new technologies.

So, to the "lemming" who thought I was a bot: I'm not. I'm just a developer who likes KMP, hates unnecessary API fees, and occasionally enjoys a piece of "trash" code that eventually may turn into treasure.


  1. At that time it was still called Kotlin Multiplatform Mobile (KMM) for those who remember. ↩

From mobile to desktop: our journey towards a new platform

Premise

KMP is a remarkably flexible and unintrusive technology: it allows you to choose exactly which code to share between platforms without forcing any specific architectural patterns on you.

We've discussed its key advantages and why it's a compelling choice for building Fediverse apps in a previous post.

Self-deception

However, Raccoon has always been highly Android-centric. For a long time, I targeted only one platform, tested only on Android, and distributed only via APKs and app bundles. iOS-specific code was written where necessary during development, but it often felt like a shot in the dark.

Occasionally I would run a debug build on an iOS simulator just to gaslight myself into believing this was a truly multiplatform project, even when all factual evidence pointed to the contrary.

Reality check

But sooner or later, the moment to face reality arrives. For me, it came when I was invited to speak at this year's DevConf 2026 about Raccoon. I only had one issue: the app had mostly seen bug fixes and maintenance over the last year. As I was heading toward a stable 1.0 release, I lacked "cool and shiny" new features to showcase at a conference.

Then it hit me: I could finally fulfill the requests from my Linux users and build a desktop version. This would allow me to present not just a mobile app, but a cross-platform open-source client for the federated social web.

I decided to dedicate a weekend or two to porting the app, promising to pivot if it didn't feel worth the effort. To my surprise, after just one night of work, I had a macOS app running. It was missing features and the layout was… questionable, but it worked and this was enough to give me fuel to continue the work!

Spoiler

We eventually decided to switch topics for the conf (stay tuned!) but Raccoon will still be making an appearance!

Our journey towards a new platform

In this article, I'll share my journey of supporting a new platform, highlighting the most critical hurdles I encountered. Here's what we'll cover:

  • Build configuration
  • Adding an entry point
  • Native implementations
  • The OAuth2 Challenge

Build configuration

This boiled down to configuring the org.jetbrains.kotlin.multiplatform Gradle plugin for a new target. Luckily, Raccoon uses convention plugins (more on this in a future post). Since I already had acom.livefast.eattrash.kotlinMultiplatform plugin, I only had to add jvm() within a single extension function to automatically update over 50 subprojects.

Admittedly, there's a bit more to it since I'm also using CMP. Similarly, I updated my com.livefast.eattrash.composeMultiplatform convention plugin to adapt the UI layer across all affected subprojects.

Adding the entry point

Every Java-based application needs an entry point. I initially created a main() function in the :shared subproject, but later moved it to a dedicated :desktopApp module. This aligned the project structure with the new JetBrains defaults, which we discussed in a previous post.

Native implementations

This is where I spent the bulk of my time: adding actual versions for all expect declarations.

Sometimes this was trivial because I used libraries that already offered JVM support, such as:

Other times, it was difficult because no JVM equivalent existed for specific mobile functionalities (like Moko-permissions or UnifiedPush).

Even seemingly simple tasks turned out to be tougher than expected. For instance, reading app metadata (version and build number) is intrinsically coupled with how the application is packaged for each specific platform.

The OAuth2 Challenge

The OAuth2 login flow deserves its own spotlight. Typically, you redirect the user to an external provider in a webview, they authenticate, and then redirect back with a code you exchange for a token.

On mobile, this relies on deep linking (custom URI schemes) so the OS knows to pass the data back to your app. Web apps just point back to a backend server.

But what about desktop apps? I initially tried using the Calf webview, but it struggled with intercepting the redirects needed for the OAuth2 flow.

The solution? I leveraged Ktor — already in use for network calls—to spin up an ephemeral local server. The redirect URI points to localhost on the first available port. The server stops as soon as the request is intercepted or after a short timeout. It turned out to be an elegant and seamless solution.

Conclusion

This journey was incredibly enriching. It pushed me to improve the layout for larger screens, which—as a side benefit — greatly improved the experience for mobile users on tablets and foldables.

While it might seem like a small addition, providing a .deb package for every release is a win for the open-source community. Having a dedicated desktop Fediverse client helps the ecosystem grow, and I'm excited to see where the project goes next.