A Field Guide to GTK Widgets

A Field Guide to GTK Widgets

This is the opening post of a series that works through GTK4 and Libadwaita together, one practical interface decision at a time. The posts are grouped around the things you actually build — a window, a list, a settings page — rather than the library a widget happens to ship in. Each post stands alone; together they’re meant to be the field guide the reference documentation isn’t. The problem with the reference The GTK4 documentation is genuinely good. Every widget has a page. Every property, signal, and method is listed. The class hierarchies are accurate and the prose is precise. If you already know which widget you need, the reference will tell you everything about it. ...

June 5, 2026 · 9 min · Justin
Building GNOME Apps with Rust, Part 5: State and Signals

Building GNOME Apps with Rust, Part 5: State and Signals

This is Part 5 of a series taking a GNOME app from an empty directory to GNOME Circle. Part 4 replaced our XML templates with Blueprint and grew the window into the real Gazette layout — a sidebar, a content pane, and three typed widget handles waiting for behaviour. This is the post where they get some. If the GObject machinery in here feels unfamiliar — mod imp, properties, signals — Part 2 is the reference. This is where those patterns stop being theoretical. ...

June 2, 2026 · 20 min · Justin
Four Ways the Outbox Bit Me

Four Ways the Outbox Bit Me

In the previous post I described an architecture where the Moments library doesn’t know that sync exists. Services record Mutation values into a one-method trait; one implementation writes to an outbox table, another does nothing. The library code is identical regardless of which backend it’s running under, and a third backend could be added without touching the library at all. The architecture is sound. I still believe in it. The first six weeks of running it were a different story. Four bugs in particular are worth describing, because each one tells you something the diagrams don’t. Two are about the abstraction — assumptions the trait quietly makes about how the world works, and what breaks when the world doesn’t cooperate. Two are about the substrate — the boring queue-and-retry machinery underneath the trait, which has its own opinions about how things should be done. Clean architecture doesn’t relieve you of the substrate’s problems. It just gives you one place to deal with them. ...

May 31, 2026 · 12 min · Justin
Why Open Source Doesn't Embrace AI

Why Open Source Doesn't Embrace AI

A week ago I would have told you the open source community rejected AI for four obvious reasons. Then I tried defending each one properly. Most of them collapse under scrutiny — and what’s left is a much more tractable conversation. The conventional answer Every maintainer I know has roughly the same three or four complaints about AI tooling. First, nobody can tell where the training data came from, which sits badly with a culture obsessed with provenance. Second, maintainers are drowning in AI-generated PRs and CVE reports — Daniel Stenberg’s posts on the curl side of this are now the canonical example. Third, AI shortcuts the old apprenticeship loop where contributors learned a codebase by earning review trust over months. And fourth, the power asymmetry: frontier models need hyperscaler capital, which sits uneasily inside a movement built on “you can read, modify, and redistribute the thing.” ...

May 27, 2026 · 10 min · Justin
Recording Mutations, Not Events

Recording Mutations, Not Events

Half of the library code in Moments, my photo manager, calls a one-method trait after every database write. That trait has two implementations. One of them returns Ok(()) and does nothing else. From inside the library, there is no way to tell which one is wired up. That is the entire architecture of how Moments syncs to Immich — and the entire reason the local backend, which has no sync at all, is the same program as the Immich one. ...

May 17, 2026 · 13 min · Justin
The Shape of Decoupling

The Shape of Decoupling

There is a moment in every codebase where someone draws a box on a whiteboard, labels it EventBus, and draws arrows fanning out to every other box. The room nods. It looks like architecture. It looks like the right architecture — loosely coupled, extensible, the textbook answer to “how do we let many components react to one thing happening?” That diagram is a trap. I drew it for Moments, my photo manager, in late March 2026. I had the design reviewed by an external UI architect. I shipped it in April across six phased PRs. And on the third of May I deleted the whole thing in a single commit titled, with some satisfaction: ...

May 10, 2026 · 8 min · Justin
Building GNOME Apps with Rust, Part 4: Blueprint

Building GNOME Apps with Rust, Part 4: Blueprint

This is Part 4 of a series taking a GNOME app from an empty directory to GNOME Circle. Part 3 walked through every file Builder generated for our gazette project. Now we’re going to start changing things. If you’re new to this stack and wondering why GTK and libadwaita are separate libraries, why GObject’s type system feels like 1990s C, or why Flatpak ships its own runtime alongside your app, there’s a short companion piece on the history of the stack. Skim it for context or skip it for code. ...

May 6, 2026 · 14 min · Justin
Building GNOME Apps with Rust, Bonus: The Stack Underneath

Building GNOME Apps with Rust, Bonus: The Stack Underneath

This is a bonus post in the series taking a GNOME app from an empty directory to GNOME Circle. It sits between Part 3 and Part 4 — read it if you want context, skip it if you want code. By the end of Part 3 you had a running GTK 4 + libadwaita app with five trait implementations, a mod imp block, an adw::Application parent class, a meson.build next to your Cargo.toml, and a Flatpak manifest pinning a runtime version. None of those decisions arrived in 2026 fully formed. Each is a fossil of a specific argument that took years to settle. ...

May 5, 2026 · 8 min · Justin
Building GNOME Apps with Rust, Part 3: Your First App

Building GNOME Apps with Rust, Part 3: Your First App

This is Part 3 of a series that takes a GNOME application from an empty directory to acceptance into GNOME Circle. Part 2 covered GObject’s type system — properties, signals, and the inner/outer type pattern. Now we’ll use everything we learned to build a real application. From theory to a running window In Part 2 we built a GObject subclass by hand — a Feed model with properties and signals, no GTK in sight. That was deliberate. Understanding GObject’s inner/outer type split, the ObjectSubclass trait, and the mod imp pattern is the foundation that everything else rests on. ...

May 3, 2026 · 16 min · Justin
Making a Dumb Fridge Smart

Making a Dumb Fridge Smart

There’s about $400 of meat, milk, and miscellaneous condiments in my kitchen fridge at any given time. It runs 24/7, makes a quiet humming noise, and gives no indication when something’s wrong until you open the door three days later and recoil. The freezer compartment is worse: a slow failure can defrost everything before you notice the puddle. I already had a TP-Link P110 smart plug on the fridge — originally for energy monitoring, because I’m on a spot-priced electricity tariff and I like knowing what each appliance costs me. But the same wattage stream that tells you “the fridge used 1.4 kWh today” tells you almost everything you need to know about whether the fridge is healthy. ...

May 3, 2026 · 9 min · Justin