From Zero to Linux in Days: How I ported GitBrowser, with Elements, Earth, and GTK RemObjects Software ported its GitBrowser desktop application to Linux with GTK 4 in just three days, from July 27 to July 30, 2026, achieving functional parity and producing a self-contained Flatpak. The rapid port was enabled by shared architecture across the Fire, Water, and Earth versions, with the Linux version adding only a presentation and lifecycle layer to existing portable behavior. Porting a mature desktop application to a third operating system is usually measured in months. The visible interface is only the beginning: application lifecycle, settings, process execution, menus, dialogs, licensing, packaging, updates, and dozens of small platform conventions all have to be reconsidered. Our Linux port of RemObjects GitBrowser was different. GitBrowser already existed as a native macOS application and a Windows application. In late July 2026, we started adding a standalone Linux version built with GTK 4. The first Linux commit, and the commit bringing the main window to functional parity landed on July 27. Clone, publish, preferences, licensing, AI-assisted change analysis, image diffs, repository management, and the remaining secondary interfaces followed on July 28. By July 30 the work was integrated into the main development branch and producing a self-contained, shippable Flatpak. This was not because GTK somehow made desktop development trivial, nor because the Linux version was a web application wearing a desktop shell. It is a real GTK application with native menus, windows, dialogs, trees, tables, gestures, file pickers, and desktop integration. The port moved quickly because several architectural decisions, made across Fire, Water, Earth, GitBrowser, and the Elements toolchain, happened to reinforce one another. Together they turned the job from “rewrite an application for Linux” into “add a Linux presentation and lifecycle layer to an application whose behavior was already portable.” That distinction made almost all the difference. The application we did not have to rewrite GitBrowser is more than a repository viewer. It manages groups of repositories and worktrees, displays commit history and changed files, renders text and image diffs, performs staging and commit operations, supports branches and stashes, clones and publishes repositories, integrates external diff tools, and uses yours truly, CodeBot, for changeset analysis and commit-message generation. Most of that behavior did not live in an AppKit view controller or a WPF window. It already lived in shared controllers, models, and services under modules such as: SharedGitBrowser SharedSourceControl SharedCodeEditor SharedCoreApp SharedBaseLibrary The macOS and Windows applications supplied their respective presentation layers, but repository state, Git operations, worktree semantics, validation, persistence, AI integration, and most commands were shared. This meant that the Linux port did not begin with a list of Git features to reimplement. It began with a list of UI surfaces to host. The repository tree already knew how repository groups, repositories, and worktrees behaved. The commits controller already knew how to load history and represent the working tree. The changed-files controller already knew how to stage and unstage files. The diff controller already knew which content to request. The clone and publish controllers already owned their validation and operations. Preferences already had a shared settings model. The GTK work was therefore concentrated at the boundary: - create widgets and windows; - connect GTK signals to existing actions; - translate shared rows and nodes into GTK list and tree presentations; - synchronize shared controller state back into widgets; - adapt lifecycle and desktop conventions; - package the resulting application for Linux. That is still real engineering work. The finished shared GitBrowser module contains 18 GTK-specific implementation files and 14 GTK Builder .ui resources. But those files are adapters around an existing product, not a second implementation of the product. Fire, Water, and Earth had already drawn the boundaries The broader Fire codebase has three desktop personalities: Fire is the native macOS application, using Cocoa and AppKit. Water is the Windows application, using WPF. Earth is the Linux application, using GTK, which i recently finished implementing myself. Not to brag. The important part is not that the three applications use different UI toolkits. The important part is where they are allowed to differ. Shared behavior belongs in Shared modules. Toolkit-specific code remains in partial files such as: SomeController.cs SomeController.Cocoa.cs SomeController.WPF.cs SomeController.GTK.cs The shared partial owns state, actions, validation, and feature logic. The platform partial loads the view, connects events, and performs the smallest practical amount of widget-specific synchronization. This is not an attempt to invent one universal control abstraction that pretends NSTableView , WPF DataGrid , and GTK lists are the same widget. They are not. Trying to hide every difference would create an abstraction more complicated than any of the three toolkits. Instead, the architecture shares the controller contract and permits each toolkit to present it naturally. On macOS an action may be connected directly from a XIB. WPF may forward a XAML event into the same shared action. GTK connects a signal in the .GTK.cs partial. The user sees the same command and the shared controller performs the same operation, while each toolkit retains its normal lifecycle and event model. Earth was especially valuable to the GitBrowser port because it had already solved the general GTK problems. GitBrowser could reuse Earth’s base library, code editor, project system, core application support, and source-control layers without becoming part of the Earth application. That separation was intentional. The Linux executable references: EarthBaseLibrary EarthCodeEditor EarthCoreApp EarthSourceControl It does not reference EarthApp . GitBrowser is therefore a standalone application with its own application identifier, resources, entry point, window, menus, settings, and package. Earth provided reusable Linux infrastructure, not an application shell that GitBrowser had to impersonate. This “reuse the layers, not the app” rule kept the dependency graph clean and the product identity honest. Elements made the shared architecture concrete Good source organization alone would not have been enough. The Elements compiler and runtime model are what allowed the same architecture to become actual executables on three platforms. One codebase, multiple runtime families The shared GitBrowser code is largely written in RemObjects C and Oxygene. Elements can compile that code for different runtime families. It uses Elements RTL rather than platform APIs, for virtually all code. On macOS, Fire and GitBrowser use the native Toffee toolchain. Water and Earth use Echoes, the Elements .NET toolchain. The Linux GitBrowser target is a .NET 10 application using GTK through GirCore. The significant point is that moving from native macOS to .NET on Linux did not require translating the shared application into another language. The same controller and model sources participate in each target build. Platform conditions still exist, but they describe real boundaries instead of selecting separate applications. Runtime differences use conditions such as TOFFEE and ECHOES . Product and toolkit differences use FIRE , WATER , and EARTH . That distinction exposed one of the most instructive bugs in the port. Some shared project selections historically treated ECHOES as synonymous with WPF. That was valid while Water was the only Echoes desktop target. It became wrong the moment Earth and GitBrowser also targeted Echoes on Linux. The fix was not to add more exceptions. It was to model the axes correctly: - select WPF and XAML using WATER ; - select GTK partials and Builder resources using EARTH ; - use ECHOES only for behavior genuinely shared by both .NET targets. Once the project described the real architecture, the compiler became an effective guide to any remaining assumptions. Shared project items instead of copied projects GitBrowser’s shared sources are defined in SharedGitBrowser.projitems . The macOS, Windows, and Linux library projects import those items and select the correct toolkit files through project conditions. A simplified version of the arrangement looks like this: