Your First Factory: GtkListView and the Bind/Unbind Rhythm

Your First Factory: GtkListView and the Bind/Unbind Rhythm

This is the fourth entry in A Field Guide to GTK Widgets — a series about which widget to reach for, when, and what bites you when you do. The previous post got a list on screen with a factory it deliberately didn’t explain. This one explains it. The complete, runnable code lives in the companion repo. Where we left off The list-mindset post got a GtkListView on screen with just enough factory code to prove the model-driven approach worked, and it said so directly: setup builds a row’s widgets once, bind fills them in, and GTK recycles rows as you scroll — the same widgets get re-bound to different items instead of new rows being built for each one. That recycling is the entire reason GtkListView scrolls well through ten thousand rows without breaking a sweat, and it’s also the thing that breaks people’s mental model the first time they hit it, because nothing in the API shouts about it. You write bind once, it looks like a constructor, and it isn’t one. ...

August 18, 2026 · 8 min · Justin
Building GNOME Apps with Rust, Part 6: Fetching Feeds

Building GNOME Apps with Rust, Part 6: Fetching Feeds

This is Part 6 of a series taking a GNOME app from an empty directory to GNOME Circle. Part 5 wired the sidebar to a real Feed GObject and a feed-selected signal — selecting a row updates the content pane and prints a line to the terminal. This is the post where that line stops being a placeholder and becomes a real network fetch. The click that still does nothing real Select This Week in GNOME, then select Hacker News. The placeholder swaps in the new name and URL — everything the app knows about that feed, and none of what’s actually in it. Every Feed in the sidebar has a uri pointing at a real RSS or Atom document on the actual internet, and nothing in the codebase has ever asked one of them what’s there. ...

August 11, 2026 · 19 min · Justin
Stop Thinking in Rows: GListModel and the Modern List Mindset

Stop Thinking in Rows: GListModel and the Modern List Mindset

This is the third entry in A Field Guide to GTK Widgets — a series about which widget to reach for, when, and what bites you when you do. We’re jumping straight to lists, ahead of layout and navigation, because it’s the material the reference documents worst. Each post stands on its own, and the complete, runnable code for this one lives in the companion repo. The list you’d build without thinking Say you’re showing a handful of tasks. The obvious way to do it doesn’t require reading any documentation at all: ...

August 11, 2026 · 10 min · Justin
The Application Shell: GtkApplicationWindow vs AdwApplicationWindow

The Application Shell: GtkApplicationWindow vs AdwApplicationWindow

This is the first proper entry in A Field Guide to GTK Widgets — a series about which widget to reach for, when, and what bites you when you do. We start where every app starts: the window. Each post stands on its own, and the complete, runnable code for this one lives in the companion repo. Two windows, and the one you should reach for You sit down to write a GNOME app. The very first widget you need is the window, and the toolkit immediately hands you a choice it doesn’t explain: GtkApplicationWindow, from GTK itself, or AdwApplicationWindow, from Libadwaita. The reference describes both accurately and tells you nothing about which one you want. ...

June 12, 2026 · 9 min · Justin
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
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