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.

This is the short version of those arguments. The platform you’re writing against is shaped by five fights — over licence, components, design, toolkit independence, and distribution — and once you’ve heard the fights, the conventions stop feeling arbitrary.


1. The licence fight (1996–2000)

In 1996 the dominant Unix GUI toolkit was Motif, owned by the Open Software Foundation and proprietary in a way that mattered: you couldn’t ship free software against it without commercial licensing. Spencer Kimball and Peter Mattis, two Berkeley undergrads writing the GIMP paint program, wrote their own toolkit instead and called it the GIMP ToolkitGTK. Within a few years the toolkit had outgrown the paint program. It is now what most of the free desktop is built on.

The same year, Trolltech released Qt 1.0 and Matthias Ettrich announced KDE on top of it. Qt was technically excellent, but its licence wasn’t one the Free Software Foundation recognised until 2000. Between 1996 and 2000, a substantial part of the GNU community considered KDE non-free regardless of source availability.

In August 1997, Miguel de Icaza and Federico Mena announced GNOME — explicitly framed as a free-software desktop that didn’t depend on Qt. The technical justifications (component model, accessibility, language bindings) were in the announcement; the political one — you should not have to choose between a working desktop and a free-software stack — is what gave the project momentum. Qt was relicensed under the GPL in 2000 and the original objection mostly evaporated, but by then GNOME existed, had momentum, and had a 1.0 release on the way. The schism was permanent.

The through-line to your code: GNOME’s founding pressure was licence purity, not technical superiority. Builder defaults to GPL-3.0-or-later when it scaffolds a project. Flathub’s review process flags proprietary dependencies. Publishing through Flatpak rather than as a closed binary is implicit in every tutorial you’ll read. None of this is convention; it’s the residue of an argument from 1996.


2. The component dream that didn’t pan out (1999–2008)

GNOME 1.0 shipped in 1999 against GTK+ 1.2. The project’s name was the GNU Network Object Model Environment, and the network object model part was load-bearing. GNOME wasn’t supposed to be a window manager and a panel. It was supposed to be a full component architecture: applications built out of embeddable, scriptable, network-transparent objects living inside each other’s windows. The technology underneath was CORBA, and GNOME shipped its own implementation (ORBit) plus a higher-level component framework (Bonobo). The dream was that you’d embed a GNumeric spreadsheet inside an AbiWord document and edit it in place.

This wasn’t a GNOME-specific bet. The whole industry was on it: Microsoft’s COM and OLE, Sun’s Java RMI, a decade of conference talks predicting that desktop apps would be rebuilt as orchestrations of small composable objects messaging each other. It didn’t pan out. Components-via-IPC was the wrong abstraction at the application layer, and the lesson got learned the expensive way by everyone who’d bet on it.

The technology survived in a different shape. GLib 2.0 had shipped in 2002, and one of its contributions was to formalise GObject as a standalone type system — separable from GTK, and from the Bonobo component dream above it. That is the type system you’re working against in Part 2’s Feed and Part 3’s GazetteWindow. The shape of it — inner/outer split, vtables, manual reference counting, registration macros — is a 1990s C solution to the object-oriented programming problem. It feels strange in Rust because it predates Rust by fifteen years. D-Bus survived for the same reason: deliberately small where CORBA was deliberately general, and it’s how your single-instance behaviour works. GObject Introspection (2008) ships machine-readable type metadata alongside every GObject library, and it’s the only reason the Rust bindings can be auto-generated and stay current.

A lot of GNOME’s strangeness in 2026 is the residue of a 1990s bet that didn’t pay off. The bet itself is gone. The infrastructure built to support it survived, got refactored, and is now the foundation everything else stands on.


3. The GNOME 3 reset (2011)

GNOME 2 had been the standard Linux desktop for a decade by 2010 — a panel, a menu, a taskbar, icons on the desktop. Boring, in the best sense.

April 2011 broke that. GNOME 3.0 shipped with a complete reset of the user-visible desktop: a full-screen Activities overview, dynamic workspaces, no taskbar, no minimise button, no system tray, no desktop icons. The argument from the design team was that those affordances were artefacts of a 1990s desktop that no longer matched how people actually used computers. The revolt was real and loud — whole distributions refused to ship it, two forks (MATE preserving GNOME 2 verbatim, Cinnamon fixing the new shell) emerged within a year, and Linus Torvalds publicly switched to Xfce.

Here’s the part that’s harder to write in 2026 than in 2012: the design has aged well. The Activities overview, dynamic workspaces, the deliberate absence of customisation knobs, the bias toward defending users from a thousand small decisions — these were unpopular in 2011 because they were unfamiliar. macOS, Windows 11, ChromeOS, every mobile OS — all the design idioms now look more like GNOME 3 than they look like GNOME 2.

Every libadwaita widget you reach for is a descendant of that design ethos. Adw.ApplicationWindow instead of Gtk.Window, Adw.ToolbarView containing the header bar, the absence of a system-tray API, the deliberately limited theming surface — these aren’t arbitrary library decisions. They are the same opinionated-design argument from 2011, restated as code. Building Gazette to libadwaita conventions is, structurally, accepting GNOME 3’s premise. Fifteen years on, the premise has stopped being controversial.


4. GTK 4 and the libadwaita split (2020–2021)

GTK 4.0 shipped in December 2020 after roughly four years of development. The whole rendering pipeline had been rewritten — widgets emit declarative render nodes that GSK (the new GTK Scene Kit) compiles into GPU-accelerated draw operations. That’s why your Flatpak manifest from Part 3 has --device=dri even though you’re shipping an RSS reader. It’s not for graphics; it’s because GSK exists, and GSK wants the GPU.

The other GTK 4 change is harder to see but more consequential. The toolkit was deliberately positioned this time not to be the GNOME-only library any more. GTK 3 had dragged the GNOME aesthetic along with it wherever it went. GTK 4 strips the GNOME-specific visual identity out and leaves a place where it can be added back, optionally, as a separate library.

That library is libadwaita, first released in December 2021. Its release announcement put the bargain plainly: GTK should grow independently of GNOME at its own pace; other GTK consumers — Inkscape, elementary OS — should be first-class citizens rather than second-class baggage; libadwaita itself should be another library you can choose to link against if you want to make your application fit well into GNOME. The Adwaita stylesheet — GTK 3’s default look — moved out of GTK into libadwaita, and GTK 4 kept a renamed neutral copy.

Each adw:: import in application.rs and window.rs is a deliberate vote to be a GNOME app rather than a generic GTK one. Replace every adw:: symbol with its gtk:: equivalent and Gazette would still compile, still run, and still display a working window. It would just stop being a GNOME application. The line between the two is exactly the line GTK and libadwaita’s architects drew in 2020 and 2021, restated in your use statements.


5. Flatpak and the runtime model (2015–)

Before Flatpak, a GNOME application’s distribution problem was N × M × K. Forty-odd Linux distributions, each shipping a different GTK version, each on a different cadence, often two GNOME releases behind. When a user reported a bug you’d fixed eighteen months earlier, the answer was almost always which distro, which version, please file a bug with them. That isn’t a release model. It’s an apology.

Flatpak (originally xdg-app, started 2015 by Alex Larsson at Red Hat) ignores the host distribution’s library versions entirely. Bundle the application with the runtime it expects, run it in a sandbox via bubblewrap, mediate everything else through portals. If your app needs org.gnome.Platform 50, Flatpak ships it alongside your app and the user’s Debian or Fedora version is no longer your problem.

The runtime/SDK distinction in Part 3 is the literal embodiment of this model. org.gnome.Platform is what users get; org.gnome.Sdk is what you build against. Updating your manifest from "runtime-version": "48" to "50" isn’t a build-system quirk. It’s the act of saying I have tested this app against GNOME 50, here is what I’m shipping. The Flatpak manifest pinning a version, the --socket=wayland line, the --device=dri line — every one of those is a public commitment to a specific platform contract.


So

Each shape in your Part 3 project has a date and a reason now. The five trait implementations: GObject, formalised in 2002 because the 1990s component dream needed a separable type system. The mod imp block: that same 1990s C inheritance pattern restated as a Rust idiom. The adw::Application parent class: the 2020–2021 split between a deliberately neutral GTK 4 and a deliberately opinionated libadwaita, drawn in use statements. The meson.build next to Cargo.toml: the 2017–2019 migration that replaced autotools across the project. The Flatpak manifest: the 2015-onward distribution model that made same app, all distributions a viable promise.

History over. Part 4 is where the code resumes.