So, I Vibe Coded A Way Out, Again.
A while ago I wrote an article asking When Is 100% Vibe Coding OK?. My conclusion was that it works when the human already owns the invariants. If you know the problem is solvable, understand the constraints, and have a reliable way to verify the output, AI can be a fantastic accelerator. Apparently I decided to test that theory again. This time with my banking app. If you are savvy enough in the Go ecosystem and you just want to produce QR codes that most banking apps can consume here's the "TL;DR just show me the code" version: The rest of this article is for those who are interested in how the sausage was made. The "paper" problem I live in Germany. Germany loves paper. I don't. Well, I do, but in its original form, as trees 🌳🌳🌳 🌳🌳🌳 🌳🌳🌳 Here, lots of invoices arrive in envelopes. Notices arrive in envelopes. Things that absolutely could have been an email arrive in envelopes, and because I am terrible at filing them, they pile up. Eventually I perform the ancient German ritual of sitting down next to the pile and dealing with it. This is when the second annoyance begins. Somewhere on each invoice are the recipient, IBAN, amount and payment reference. I open my banking app and start copying them (nowadays the app can photograph the page and you get a decent prefill - but I still check each field). IBAN, check it. Amount, check it. Reference, check it. Look back at the paper because I no longer trust myself. Check everything again. There has to be a lazier way. Wait, isn't this what QR codes are for? My first thought was extremely sophisticated: Can I somehow shove all of this into a QR code? Turns out others in Europe had the same idea 14 years ago 😀 There is an EPC QR code standard for SEPA credit transfers, commonly encountered as GiroCode. Underneath the banking terminology is something wonderfully boring (just how I like it): structured text. Something along these lines: BCD 002 1 SCT Example Recipient DE69670400440672013000 EUR12.34 Invoice 2026-001 Encode a correctly structured EPC payload as a QR code, point a compatible banking app at it, and the payment form gets populated. Recipient, IBAN, amount, reference. Exactly the boring automation I wanted. - note that you can only transfer a maximum of 999.999.999,99 EUR so if you need to transfer more than a billion you can't use this method. Linux already had most of my application I could have started looking for QR libraries. Instead, Linux already had qrencode. Give it text. Get a QR code. So the first architecture was basically: payment fields ↓ EPC payload ↓ qrencode ↓ PNG This is my favorite kind of software architecture: boring. The EPC specification defines the payload. qrencode already knows how to make QR codes. Neither problem needed me to invent anything. What I actually needed was some glue around them. And this is where Codex shines. Enter Codex I didn't really want to spend my free time typing GO boilerplate. I knew what I wanted the program to do, I knew the boundaries, and, most importantly, I had an extremely good test oracle sitting in my pocket: my banking app. To be clear I didn't ask Codex to just "build the thing". I started with the project structure. Once that was good, implement the EPC payload and tests. Good. Add the qrencode boundary. Good. Now generate a self-contained HTML document. Good. Now make the CLI accept a real payment. Small step -> test -> commit -> iterate. At some point in this cycle I felt less like someone programming in the traditional sense and more like Tony Stark talking to Jarvis. "Good. Now throw a database in there." Except with far less holographic UI and considerably more: go test ./... go vet ./... The database was SQLite, of course, because of the architectural principle mentioned before: pick the most boring thing that solves the problem. The HTML followed the same philosophy. The QR PNG is embedded directly into the document as Base64 encoded text. One file contains the payment details and its QR code. No server, no framework and no external assets required to display it. At this point in time the pipeline looked like this: payment fields ↓ EPC payload ↓ qrencode ↓ PNG ↓ self-contained HTML ↓ SQLite Then came the important test. And then I made a donation with it Eventually I could run something like: go run ./cmd/giro create \ --recipient "Wikimedia e. V." \ --iban DE09370205000003287300 \ --amount 6.79 \ --reference "Via LAZY-EPC over GitHub" The application validates the payment, creates the EPC payload, asks qrencode for the PNG, embeds it into the HTML document and archives the result in SQLite. generated ~/Documents/payments/payment-20260816-184212.html stored payment #7 I opened the generated document, pointed my banking app at the QR code, and everything appeared in the correct fields. xdg-open ~/Documents/payments/payment-20260816-184212.html And then, because apparently I have more faith in my software development process than is healthy, I actually made a real transfer with it. It worked. That probably should have been the end of the project. Once I had used it for a real payment, another obvious annoyance appeared. A lot of these payments are similar. Same recipient, same IBAN, perhaps a different amount or reference. I already had SQLite. Why should I type all of that again? So the database gained search and notes. Previous payments could become the starting point for new ones. Find the old payment, change what changed, generate another QR. This solved the data problem, but now I needed some way to browse it and that's when a roughly 30-year-old memory came back. A Romanian post office, sometime in the late 1990s I grew up in Romania, and sometime in the 1990s I remember seeing computer terminals at the post office. I couldn't tell you what computers they were. I don't know what operating system they ran, and I for sure couldn't tell you anything about the software behind them. But I remember the interface. Those wonderful old forms. Boxes, rows, fields, keyboard navigation. No mouse required. No animation. No hamburger menu. It looked incredibly serious to me as a kid. It looked like a computer. Thirty years later, while building software specifically because I don't want to deal with paperwork coming through the post, I realized exactly what interface I wanted. "A post-office terminal, from the 90s" Apparently some circles take thirty years to come around. So I went back to Codex. "Give it a TUI." The result looks exactly as dated as I wanted it to. The SQLite archive becomes a terminal table. I can filter it by recipient, reference, notes or IBAN, navigate through previous payments and press Enter on one. That opens a form containing the complete payment. Tab through the fields, change the amount or reference, and create a new one. There are no recurring-payment templates because, after using the application, I realized I didn't actually need them. The database itself is the template system. Search for the last similar payment and clone it. The notes field is deliberately local metadata. It never enters the EPC payload. It's there so Future Me has some chance of understanding what Past Me was doing. And yes, the TUI has Create, Update, Delete and Open. Delete asks first, because even my commitment to 1990s user-interface design has limits. Boring is a feature There are already plenty of things I could add. The external qrencode dependency could become a small Go library so the application can eventually be distributed as one binary. The TUI could gain mouse support, more keybindings and other views. Prebuilt packages would make installation less dependent on knowing your way around the Go ecosystem. I also discovered another rabbit hole that I am deliberately not entering yet: custom URI handlers. Something like: lazyepc://payment/... could eventually let a service hand a payment to the locally installed application through the operating system. That sounds fun. Which is precisely why I'm not building it right now. The current application solves the problem that annoyed me. It generates the QR, remembers previous payments and gets out of the way. Good is better than perfect, and useful software is allowed to stop before it is mirror polished. So, was this 100% vibe coded? Pretty much. Codex wrote essentially all of the implementation. I made the architectural decisions, tested what came back, rejected complexity I didn't want and decided what the next increment should be. That distinction became particularly obvious when the TUI developed a wonderfully stupid bug: pressing Enter did absolutely nothing. At first this looked like a terminal focus problem. It wasn't. The callback was firing, but the code had returned a Go slice by value while another closure later reassigned its captured slice. The selection callback was therefore still looking at the original empty state. AI wrote the bug. I still had to understand why the system couldn't possibly be behaving correctly. Then Codex fixed it. That experience reaffirmed my conclusion from the previous article. Vibe coding didn't remove engineering from this project. It removed an extraordinary amount of typing. And, unexpectedly, sitting. I'm in my 40s now and I've been typing essentially nonstop since university. My hands occasionally remind me of that. What I didn't expect from working this way was that while Codex was implementing something or running the test suite, I didn't have to sit there staring at it. I could get up, walk around the house, stretch, do something else, then come back and review the result. I'm not going to pretend that AI-assisted programming is some kind of ergonomic treatment, but reducing the amount of time I spend glued to a keyboard turned out to be a surprisingly nice side effect. I still decided where the boundaries belonged. I decided that calling qrencode was preferable to adding another abstraction just because it was possible. I decided HTML was enough. I decided SQLite was enough. I decided what needed tests, what constituted success, and when a feature wasn't worth building. Most importantly, I decided whether I trusted the result. The invariant was sitting in my pocket This project had unusually strong invariants. EPC is a specification. The payload is inspectable text. The QR code is machine-readable. At the end of the chain is an independently developed banking application that parses the result and shows me exactly what payment it thinks I am about to authorize. Codex could hallucinate an implementation detail. It could choose a bad abstraction. It could misunderstand what I wanted the TUI to do. But it couldn't negotiate with my banking app. Either the QR code represented the payment I intended, or it didn't. That is what I meant when I wrote that the human needs to own the invariants. This time I just happened to own them while repeatedly saying the software-engineering equivalent of: "Jarvis, throw in a little hot rod red in there." The result is lazy-epc, and the code is now public. It's a workable MVP rather than a polished consumer application. If you're reasonably comfortable around Go and Linux, you can build it, use it, break it, or make it better. For now, it does exactly what I wanted: Turn annoying payment details into a QR code, remember them for next time, and get out of the way.
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to