All field notes
article10 Oct 2026

Local Haunt: a little ghost for the servers you left behind

Abandoned development servers gave me an excuse to try Rust and GPUI. A little menu bar app, familiar layout concepts, and some cursed method chains.

So the other day I was watching a Syntax video and in it, Scott Tolinski was raving on about his latest infatuation, GPUI. What is GPUI, you ask? Well, it's the very opinionated UI framework for Rust by the makers of the lovely Zed editor, built for their own editor.

I’m Using GPUI For EverythingSyntax #1043, with Scott Tolinski and Wes Bos.youtube.com/watch

While Wes Bos did call Scott out rather accurately for always being enthusiastic about the next big thing™ the conversation flowed much around why you'd use something like Rust with GPUI rather than your more typical workflow involving Electron or Tauri. I think this comparison was flawed from the start, since these tools give you rather different ways to build an app.

But what really caught my attention was the very cursed syntax used by GPUI. There was just something so wrong with it, yet it was entirely understandable for a lowly web dev like me.

let mut notice = div()
    .px_3()
    .py_2()
    .flex()
    .items_center()
    .gap_3()
    .flex_shrink_0()
    .border_t_1()
    .border_color(colors.border)
    .text_size(px(metrics.small))
    .text_color(colors.secondary)
    .child(div().flex_1().child(message));

It's like someone thought that the best idea was to take all the worst parts of Tailwind, like the obnoxious class bloat, together with some of the ugliest chaining syntax you've ever seen since jQuery. Yet somehow it's better than anything I've seen for Python.

Somehow I was hooked. I had to see why, and how, you would choose to do this.

I needed an app idea

But the age-old question was this: "why would I even need an app"?

You're working on a JS/TS project, only to find out the port you wanted is used by another project. You can't find the terminal or editor where you started it. Perhaps an earlier session left it hanging, or your agent spawned it. Now you're googling the right way to type lsof to find what's hogging the port.

A list of ports and a way to nuke those pesky processes from orbit? Hardly an original app idea. PortKiller already does this from the macOS menu bar. Even written in Rust, too.

So why build something that already exists? I don't know. Call it weird fascination, or just the fact I needed to try something in order to truly judge it. So try it I shall.

Or well, more specifically, I made Codex set it up for me in steps.

The result is Local Haunt. A little ghost in the macOS menu bar that shows listening ports, tries to work out which project or app they belong to, and lets you stop them. Because a list of Node processes is only slightly more helpful than the terminal command I was trying to avoid.

Local HauntFind forgotten development servers and stop the ones hogging your ports.github.com/las6/local-haunt
Local Haunt showing four fictional projects, with the ocean wallpaper visible through its blurred window background.
Fictional projects, real app. The desktop wallpaper shows through the native window blur.

Familiar concepts, cursed punctuation

Padding, flex, gaps, borders: I know these things. The problem is that styling, structure and content are all mixed into the same chain. Calling it Tailwind-like doesn't help much when the utility-class bloat was what I wanted to get away from.

My first thought was to collect styles under one call, something like .class(px_3(), py_2(), ...), or pass them into the element constructor. That could make the structure easier to spot, but it still leaves the same pile of utilities sitting there.

A named style applied with GPUI Kit's refine_style could go further. The render code could say .refine_style(&notice_style(colors, metrics)) and get on with its children. The styling chain would live elsewhere, though we'd still be editing and rebuilding Rust to change it.

What I'd really like is separate style files and hot reloading, so I could change the presentation without touching the surrounding UI code. There's an experiment along those lines worth looking into. How would theme values and state-dependent styles work across that boundary? I haven't tried it yet, or either of the ideas above.

For now, I just moved the common values into theme.rs. Colors, font sizes and dimensions are shared, but the styling chains are still in the views. The separation I want remains an experiment for later.

So, was it worth it?

For a small learning project, yes. I wanted to try GPUI, and now I have an app that helps me find the servers I've left behind. That's a better way to judge it than staring at someone else's method chains.

The syntax still bothers me, but at least now I can be annoyed by it in my own code. And find what's hogging port 5173 while I'm at it.


Have a thought? get in touch

images: unsplash.com