UI tests are the guardrails an AI needs: the story of clipboardwire – David Marín Carreño (DaveFX)
Skip to content
David Marín Carreño (DaveFX)
Freelance Developer. Home Automation, WordPress, Symfony, Linux
UI tests are the guardrails an AI needs: the story of clipboardwire<br>2026-05-22 /<br>Leave a Comment
Since December I had been patiently putting up with one of those everyday frustrations that, before you notice, turn into a project. I work with two machines at once: my Ubuntu laptop running Wayland, and a Windows PC that I drive from the Linux box using Deskflow — the free continuation of the old Synergy. For keyboard and mouse it works great: one input drives both screens. But the clipboard does not sync . And it turns out Deskflow, which does sync clipboards on X11, isn’t compatible with Wayland yet.
I tried the obvious alternatives: KDE Connect and a few other contraptions of that kind. But they were as likely to work as to stop working. One day the sync was fine, the next it wasn’t, the day after you had to restart something or re-pair the devices. For a tool that needs to be invisible and always ready, that kind of inconsistency is worse than not having it.
The awkward shuffle was always the same: copy something on Linux, open Google Chat, send it to myself, switch to Windows, paste it from Google Chat there. Or the other way around. A pain every time. For anyone who uses two systems side by side, clipboard sync is one of those things you don’t notice you depend on until it isn’t there.
In some forum I found a promising alternative: ClipCascade. Linux client, Windows client, a central hub to forward messages between them. Exactly what I wanted. I opened it ready to install… and saw it was a Java application on Spring Boot. The server recommended half a gigabyte of minimum heap . To sync some text between two machines on my own network.
My reaction was immediate: no, thanks. I’m not going to have a process chewing half a gigabyte of RAM permanently just so a Ctrl+C on one machine reaches the other. I closed the browser and opened Claude Code.
"Let’s convert this to Rust"
My initial prompt was, almost verbatim: "Let’s port this application to Rust, keeping the idea but making it lighter. I want a single binary that can act as both client and server." I gave it the ClipCascade repository and let it go.
I had never written a serious line of Rust. I had read a chapter or two of the book, glanced at it on the odd curious afternoon, but nothing in production. I picked it because it met two non-negotiable requirements for this use case: a single binary, no runtime to load , and a memory-safe language . If I’m going to have a process listening on a port in my network, I’d rather any slip-up end in a panic than in a buffer overflow.
I also made one thing explicit from the start: Wayland took priority over X11 . Half the problems with tray apps on Linux come from assuming we’re all still on X11. I didn’t want to drag that debt into my own code.
The rhythm: prompts in the background
Working with Claude Code on this had a specific rhythm worth describing, because it’s not how I had imagined it would work. It wasn’t a multi-hour session glued to the screen. It was more like having a quiet intern you hand a task to and come back to later to see what they’ve done.
I’d give a high-level instruction, let it get going, and meanwhile I’d carry on with client work, or go have lunch, or deal with an email. Every so often I’d come back to the console, read the diff of what it had done, tell it where to head next, and let it go again. Short review sessions separated by long stretches where it built and I did something else.
In a couple of days — because that’s how long it took — there was something working on my network. Two clients, a hub, WebSocket messages, basic authentication. Enough to start using it.
The slump: bugs that came and went
And then came the part nobody was counting on. I’d find a bug, describe it to Claude, it would fix it. I’d keep testing. Another one appeared, I’d describe it, it would fix it… and a bit later the first one came back. Or a similar one. Or a brand-new one in the area it had just touched.
This kind of thing, frustrating with a human, is downright maddening with an AI. Because there’s no persistent memory of "we’ve been here before". No "yes, I know, I fixed that last week". There’s a system that, at each turn, does the best it can with the code in front of it, without remembering it touched the same area before.
I’d been like this for a couple of days when I realised the problem wasn’t Claude’s. The problem was that I had no way of knowing whether a fix broke something that was already fine. Neither did he, neither did I. The application is a tray app, with a graphical UI and asynchronous behaviour — exactly the kind of thing that’s hard to test by hand, and where a bug can take several uses to resurface.
The investment that changed everything: tests, UI...