{"slug": "so-i-vibe-coded-a-way-out-again", "title": "So, I Vibe Coded A Way Out, Again.", "summary": "A developer in Germany used OpenAI's Codex to build a Go-based tool that converts invoice data into EPC QR codes (GiroCode) for SEPA bank transfers, automating the tedious process of manually entering payment details into a banking app. The project, which leverages the EPC QR code standard and the qrencode library, was developed through small, testable steps with the banking app serving as a verification oracle. The developer emphasized the importance of understanding invariants and using boring, reliable technology.", "body_md": "A while ago I wrote an article asking .\n\n`When Is 100% Vibe Coding OK?`\n\nMy 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.\n\nApparently I decided to test that theory again.\n\nThis time with my banking app.\n\n*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:*\n\nThe rest of this article is for those who are interested in how the sausage was made.\n\nI live in Germany. Germany loves paper. I don't. Well, I do, but in its original form, as trees `🌳🌳🌳`\n\n`🌳🌳🌳`\n\n`🌳🌳🌳`\n\nHere, 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.\n\nEventually I perform the ancient German ritual of sitting down next to the pile and dealing with it. This is when the second annoyance begins.\n\nSomewhere 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.\n\nThere has to be a lazier way.\n\nMy first thought was extremely sophisticated: **Can I somehow shove all of this into a QR code?**\n\nTurns out others in Europe had the same idea `14`\n\nyears ago `😀`\n\nThere is an [EPC QR code standard](https://www.europeanpaymentscouncil.eu/document-library/guidance-documents/quick-response-code-guidelines-enable-data-capture-initiation) for `SEPA`\n\ncredit transfers, commonly encountered as `GiroCode`\n\n. Underneath the banking terminology is something wonderfully boring (just how `I`\n\nlike it): structured text.\n\nSomething along these lines:\n\n```\nBCD\n002\n1\nSCT\n\nExample Recipient\nDE69670400440672013000\nEUR12.34\n\nInvoice 2026-001\n```\n\nEncode 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.\n\n*- 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.*\n\nI could have started looking for QR libraries. Instead, Linux already had `qrencode`\n\n.\n\nGive it text. Get a QR code.\n\nSo the first architecture was basically:\n\n```\npayment fields\n    ↓\nEPC payload\n    ↓\nqrencode\n    ↓\nPNG\n```\n\nThis is my favorite kind of software architecture: ** boring**.\n\nThe EPC specification defines the payload. `qrencode`\n\nalready knows how to make QR codes. Neither problem needed me to invent anything. What I actually needed was some glue around them.\n\nAnd this is where `Codex`\n\nshines.\n\nI didn't really want to spend my free time typing `GO`\n\nboilerplate. 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.\n\nTo 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`\n\nboundary. Good. Now generate a self-contained HTML document. Good. Now make the CLI accept a real payment.\n\nSmall step `->`\n\ntest `->`\n\ncommit `->`\n\niterate.\n\nAt some point in this cycle I felt less like someone programming in the traditional sense and more like `Tony Stark`\n\ntalking to `Jarvis`\n\n.\n\n\"Good. Now throw a database in there.\"\n\nExcept with far less holographic UI and considerably more:\n\n```\ngo test ./...\ngo vet ./...\n```\n\nThe database was `SQLite`\n\n, of course, because of the architectural principle mentioned before: **pick the most boring thing that solves the problem.**\n\nThe `HTML`\n\nfollowed the same philosophy. The `QR`\n\n`PNG`\n\nis embedded directly into the document as `Base64`\n\nencoded text. One file contains the payment details and its `QR`\n\ncode. No server, no framework and no external assets required to display it.\n\nAt this point in time the pipeline looked like this:\n\n```\npayment fields\n    ↓\nEPC payload\n    ↓\nqrencode\n    ↓\nPNG\n    ↓\nself-contained HTML\n    ↓\nSQLite\n```\n\nThen came the important test.\n\nEventually I could run something like:\n\n```\ngo run ./cmd/giro create \\\n  --recipient \"Wikimedia e. V.\" \\\n  --iban DE09370205000003287300 \\\n  --amount 6.79 \\\n  --reference \"Via LAZY-EPC over GitHub\"\n```\n\nThe application validates the payment, creates the EPC payload, asks `qrencode`\n\nfor the `PNG`\n\n, embeds it into the `HTML`\n\ndocument and archives the result in `SQLite`\n\n.\n\n```\ngenerated ~/Documents/payments/payment-20260816-184212.html\nstored payment #7\n```\n\nI opened the generated document, pointed my banking app at the QR code, and everything appeared in the correct fields.\n\n```\nxdg-open ~/Documents/payments/payment-20260816-184212.html\n```\n\nAnd then, because apparently I have more faith in my software development process than is healthy, **I actually made a real transfer with it.**\n\nIt worked.\n\nThat probably should have been the end of the project.\n\nOnce 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.\n\nI already had SQLite. Why should I type all of that again?\n\nSo 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.\n\nThis 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.\n\nI grew up in Romania, and sometime in the 1990s I remember seeing computer terminals at the post office.\n\nI 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.\n\nThose 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.\n\nIt looked like a **computer**.\n\nThirty 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.\n\n\"A post-office terminal, from the 90s\"\n\nApparently some circles take thirty years to come around.\n\nSo I went back to Codex.\n\n\"Give it a TUI.\"\n\nThe result looks exactly as dated as I wanted it to.\n\nThe 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.\n\nThat opens a form containing the complete payment. `Tab`\n\nthrough the fields, change the amount or reference, and create a new one.\n\nThere 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.\n\nThe 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.\n\nAnd yes, the TUI has `Create`\n\n, `Update`\n\n, `Delete`\n\nand `Open`\n\n. Delete asks first, because even my commitment to 1990s user-interface design has limits.\n\nThere are already plenty of things I could add.\n\nThe external `qrencode`\n\ndependency could become a small `Go`\n\nlibrary 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`\n\necosystem.\n\nI also discovered another rabbit hole that I am deliberately not entering yet: custom URI handlers.\n\nSomething like:\n\n```\nlazyepc://payment/...\n```\n\ncould eventually let a service hand a payment to the locally installed application through the operating system.\n\nThat sounds fun.\n\nWhich is precisely why I'm not building it right now.\n\nThe 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.\n\nPretty much.\n\nCodex 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.\n\nThat distinction became particularly obvious when the TUI developed a wonderfully stupid bug: pressing Enter did absolutely nothing.\n\nAt 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.\n\nAI wrote the bug. I still had to understand why the system couldn't possibly be behaving correctly.\n\nThen Codex fixed it.\n\nThat experience reaffirmed my conclusion from the previous article. Vibe coding didn't remove engineering from this project. It removed an extraordinary amount of typing.\n\nAnd, unexpectedly, sitting.\n\nI'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.\n\nI'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.\n\nI still decided where the boundaries belonged. I decided that calling `qrencode`\n\nwas 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.\n\nMost importantly, I decided whether I trusted the result.\n\nThis project had unusually strong invariants.\n\nEPC 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.\n\nCodex could hallucinate an implementation detail. It could choose a bad abstraction. It could misunderstand what I wanted the TUI to do.\n\nBut it couldn't negotiate with my banking app.\n\nEither the QR code represented the payment I intended, or it didn't.\n\nThat is what I meant when I wrote that **the human needs to own the invariants**.\n\nThis time I just happened to own them while repeatedly saying the software-engineering equivalent of:\n\n*\"Jarvis, throw in a little hot rod red in there.\"*\n\nThe result is [ lazy-epc](https://github.com/unmanageable-software/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.\n\nFor now, it does exactly what I wanted:\n\n**Turn annoying payment details into a QR code, remember them for next time, and get out of the way.**", "url": "https://wpnews.pro/news/so-i-vibe-coded-a-way-out-again", "canonical_source": "https://dev.to/realvorl/so-i-vibe-coded-a-way-out-again-heb", "published_at": "2026-08-16 22:18:46+00:00", "updated_at": "2026-08-16 22:42:04.315586+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence", "generative-ai"], "entities": ["Codex", "Go", "EPC", "GiroCode", "qrencode", "SQLite", "OpenAI", "SEPA"], "alternates": {"html": "https://wpnews.pro/news/so-i-vibe-coded-a-way-out-again", "markdown": "https://wpnews.pro/news/so-i-vibe-coded-a-way-out-again.md", "text": "https://wpnews.pro/news/so-i-vibe-coded-a-way-out-again.txt", "jsonld": "https://wpnews.pro/news/so-i-vibe-coded-a-way-out-again.jsonld"}}