Project № IX · Case study

Krater — a desktop debugger we rewrote over a weekend

A cross-platform desktop debugger for Laravel applications — the local half of the MoonGuard ecosystem. Messages, exceptions, queries, and HTTP requests streamed in from a companion PHP package and persisted in SQLite, so the last run was still there the next morning. Prototyped in Rust/Tauri, shipped in Go/Wails after a one-weekend rewrite.

I.Problem

Laravel's debugger is a web view, and half of the work does not happen in the browser.

The debugging tool most Laravel developers reach for — Ignition — is excellent at one job: surfacing an exception inside a request. The trouble is that a modern Laravel app is only partly a request. Commands run in a terminal. Jobs run on a queue. Listeners fire on events. Livewire components re-render out of band. When one of those goes wrong, the web overlay is not where the developer is looking, and dd() only survives as long as the process does.

What was missing, for the MoonGuard team's day-to-day, was a local inspector that treated the whole app as the target, not just the request — one that captured messages, exceptions, queries, and HTTP traffic from anywhere in the app, persisted them across runs, and lived in its own window instead of fighting for space inside the browser.

dd() is a debugger with a five-second memory. We wanted one that remembered yesterday.

II.Approach

A native window, a companion package, and a SQLite file that outlives the process.

Krater was one of three pieces that made up the MoonGuard ecosystem. The Filament plugin watched production; Larvis, the companion PHP package, forwarded telemetry; Krater was where the developer looked while writing the code. Larvis exposed a larvis() helper — the persistent cousin of dd() — and pushed messages, exceptions, queries, and requests to the desktop app over HTTP. Krater wrote them to a local SQLite database and displayed them across four panes:

  • Messages — anything the developer handed to larvis(), from any execution context: controller, command, job, listener, Livewire component.
  • Exceptions — errors caught outside the request cycle, where Ignition could not reach.
  • Database Queries — every statement with its timing, with later releases adding N+1 detection, an analyzer with EXPLAIN, and an AI assistant for the confusing ones.
  • HTTP Requests — headers, session state, and response bodies, grouped per request.

The footprint was part of the brief. The team ran several Laravel apps at once; a second Electron window was not going to be welcome. Krater targeted ~18 MB on disk and ~70 MB of RAM, which ruled out Electron immediately and pushed us toward the web-tech-on-native shortlist: Tauri, Wails, Flutter, Neutralino. The first prototype was Tauri and Rust.

It reached ~65% of the feature surface before the friction became the project. The ownership model ate time we did not have, especially around threads. Diesel did not fit the SQLite-heavy shape of the app and we could not find an ORM that did. The documentation for the stack was thin enough that our own blog posts kept ending up in our search results. Mocking for unit tests was, to borrow the phrase from the post-mortem, extremely complicated.

I led the call to pivot to Go and Wails, after an honest accounting of the trade-offs — garbage collection vs. ownership, GORM vs. Diesel, goroutines vs. Rust threading, compilation times, testing ergonomics. The rewrite took a single weekend and closed the remaining 35% in the same pass. Go's built-in testing package made mocks a non-event, GORM matched how we already thought about SQLite, and goroutines unblocked the concurrent features that had been stalling in Rust. The reasoning ended up published on the MoonGuard blog as its own article.

III.Outcome

Shipped across three platforms, sat next to the editor, now retired with the ecosystem.

~18 MB
Disk footprint; ~70 MB of RAM at rest
1
Weekend to rewrite the Rust prototype in Go/Wails end to end
3
Platforms — macOS, Windows, Linux — from one codebase

Krater shipped on macOS, Windows, and Linux, distributed from moonguard.dev with a 15-day trial and, later, a Free/Pro split. It sat next to the editor while you worked, collected everything Larvis sent it, and the SQLite file meant the messages from the last run of a command were still there the next morning. For a team that was building the MoonGuard plugin in public and using its own tools to do it, the local debugger turned out to be the piece we reached for most.

The app is retired alongside the rest of the MoonGuard ecosystem — the marketing site and downloads at moonguard.dev are gone, though the repository remains on GitHub. What stayed with me was the pivot, and the weekend on the other side of it.

IV.Retrospective

The hard decision was to throw away 65% of a working prototype.

Two calls still look right. First, scoping the app to the three panes that mattered — messages, exceptions, queries, requests — and resisting the temptation to grow it into a general-purpose APM. Krater stayed small on disk because it stayed small in scope; every feature we considered adding had to justify the memory it was going to cost.

Second, the pivot itself. There is a version of this story in which we pushed through the Rust friction on principle and shipped a year later with a team that had learned a lot about the borrow checker and very little about Laravel debuggers. The honest reading of the 65% mark was that the language was costing us more than it was giving us, and the right answer was to change languages. A weekend of Go is cheap evidence; we took it.

What I would do differently is the release cadence. Krater's arc tracked MoonGuard's — a strong launch, a quiet middle, a sunset that arrived before we had decided it should. A debugger that lives next to the editor earns its keep weekly; ours went months between releases near the end, and the trust in the tool went with it. If I were doing it again inside the same ecosystem, I would tie Krater's release train to Larvis's and ship something small every month, even when the roadmap was between its interesting ideas.