# kitto is dead

· #privacy #instagram #kitto

salix@host:~/posts/kitto-is-dead

Kitto was an Instagram client for GNOME and Android. It lived for most of September, and it never got a launch post, a projects page entry, or a link anywhere on this site. The only public trace was a repository on the forge that now carries a deprecation warning at the top of its README. That warning is currently the best documentation of the project, which felt wrong, so here is the real version: what it was, why I built it, and why it is dead.

Why the silence? Two reasons. First, it was an experiment. I wanted to see whether the invio pattern would survive being pointed at a harder target, and experiments don’t need announcements. Second, I was never sure I could recommend it, and a blog post is a recommendation. Now that it’s dead I can write about it honestly, because there is nothing left to oversell.

What Kitto was

If you have read about invio or threado, the shape is familiar. One Rust core holds the logic, a GTK 4 app sits on top for GNOME and a Compose app for Android, with UniFFI in between. Kitto was the third client in that family, and the GNOME layout deliberately reused Invio’s shell, because it already worked.

It browsed your feed, Reels and profiles. You could subscribe to people without an account, keep saved posts and collections, track read state, and search the local library. You could download a single post or a whole profile. Stories never worked, and Kitto said so instead of pretending otherwise.

The part I was proud of was the archive. Every post Kitto touched could be saved to a folder on your own WebDAV server as ordinary files: JPG and MP4 media plus a JSON document with captions, timestamps and alt text. You can open the media in mpv. Point several devices at the same folder and they share the work through WebDAV locks; if a device crashes mid-download, the lock expires and another device picks up the remaining files. Automatic archiving had one rule I liked a lot: it saved what you happened to load, and it never followed a pagination cursor to fetch pages you did not ask for. When you wanted everything, there was an explicit Download profile button for that.

The deal

Kitto did not talk to Instagram. It talked to Kittygram, a set of community-run servers that sit in front of Instagram’s private API. Kitto shipped with a default instance, a picker that read the current instance list from Codeberg, and rotation: when a request failed, it tried the next instance.

That is exactly the deal invio has with Invidious, and I assumed the deal would transfer. It did not.

Why it died

The difference is what sits under the proxy. Invidious instances front a service that is, however grudgingly, meant to be read by machines. Kittygram instances front Instagram’s private API, which Meta reshapes and defends as a matter of routine. The deprecation warning I wrote says it plainly: instances go down, routes return errors, and media fetching breaks without warning. That is not a description of one bad week. That was the steady state for the whole month.

None of the breakage was a bug I could fix in Kitto. The ground moved underneath it every few days, and every workaround was one more thing that could break next week. There is a category of project where you patch and chase and it slowly stabilises. This was the other category.

And a reader is an app where reliability is the entire product. The point of building it was a calm way to read a few profiles without Meta’s app on my phone. A calm reader that fails half the time is worse than the website, because now you have the surveillance and the broken images.

There was a second problem I knew about from the start, and it is the reason I would not have shipped Kitto even if it had worked. Every profile you open travels through a volunteer’s proxy, and that proxy sees what you read. For me, on my own devices, that was an acceptable trade for an experiment. Recommending that trade to strangers on a privacy blog would have been a joke.

What survives

The engineering was not wasted, it just ended up in better places. The shared-core pattern held up fine, and the WebDAV problems I solved here, the locking, the resumable transfers, the merge-after-crash cases, are the same problems Ferry’s vault sync deals with. The instance rotation logic survives as a small function that logs which instance died, which is about all it was ever good for.

The archive design is the thing I will miss. Plain files on a server you control, metadata that documents itself, devices cooperating through locks the server already understands. If I ever build a reader again, that part comes back. The rest, I won’t rebuild.

The repository stays up with the warning on top, and there are no revival plans. If you want to read Instagram without Meta’s app, I don’t have a good answer for you. I had one for about a month, and you never heard of it.

~$