Recently we hear all the time about how AI is going to replace nearly all jobs, and in particular how nobody will need software engineers any more because AI can write all the code. I don’t believe that for a moment, any more than I believed the same thing of ICL’s much-hyped “The Last One” in 1981.
People talk a lot of about “vibe coding”, which means getting AI to write code that you have absolutely no idea how to do yourself. When it inevitably doesn’t work, you tell the AI thing what’s wrong, and it fixes it. It sounds too good to be true - and I suspect is. But AI can certainly simplify and accelerate a lot of routine coding for things like web interfaces, which probably make up well over half of all the software out there. For sure there will be job losses.
A few weeks ago I downloaded the new AI-focussed editor and IDE, Cursor. I’d heard a lot about it from a friend of mine who in addition to being a high-powered investment banker and private pilot, also spends a lot of time writing code just for fun.
I’d just been finally getting my head around the finer points of Shor’s algorithm for factoring huge numbers using quantum computing, including an optimized implementation of all the underlying non-quantum math. I asked Cursor to write the same thing, in Kotlin. It did a respectable job, though for production use, even pre-quantum, it would have taken a lot more work. Just for fun I also got it to write what must surely be the only implementation of a quantum algorithm in Cobol, too. I’ve thankfully never written any Cobol in my life, but I remember it from the Danial McCracken book that I read when I was about 16. The code looked convincing.
Then my friend wrote to me in amazement at the latest thing he had got Cursor to do. There is some pretty amazing stuff in there. His program scrapes PDF documents describing airports and other aviation stuff. It’s all completely unstructured, and varies greatly between countries. Yet with a lot of help from Cursor he has built a complete, structured database of this information and - his latest effort that amazed him - a powerful web front-end to access it.
It was time for me to do something myself. A while back I wrote a program to help solve and investigate Wordle puzzles. It started in Kotlin, but I rewrote it in C++, taking advantage of the highly parallel AVX instructions available on the latest CPUs. The user interface is a classic CLI, just as it would have been 50 years ago.
I’d often thought it would be nice to have a web front-end, that would look like the Wordle phone app plus a bunch of extra features. But I have no idea how to write a modern web GUI, using the current tools like React, and no enthusiasm at all to spend weeks getting my head around them. So nothing happened.
But this was the perfect opportunity to see what Cursor could really do. Could I just tell it to write a web front-end to my existing 25,000 or so lines of highly optimized C++?
Before I dive into the details, here’s a summary of how it went. Sometimes you tell it to create something which seems pretty complicated, and it writes a bunch of code that works first or maybe second time. That really is amazing. Other times it just goes round in circles, generating something that doesn’t work, and the correcting it based on feedback - into something that doesn’t work for a completely different reason. One item took me a whole afternoon of going round in circles like this before it finally worked.
After a couple of days of not very intensive work, I have a working front-end and the necessary back-end support. If I’d done it myself, the actual coding might not have taken much longer than that, but it would have taken weeks to learn enough about React, even with the usual help from Google and StackOverflow. And I would have gone mad with frustration trying to find a working package to interface a Rest API to C++. (Ironically, I created a very powerful interface that does exactly that for my day-job software, but the solution is way too heavyweight for what I had in mind here).
It’s surprising how often Cursor creates something which doesn’t work, then when you tell it what hasn’t worked, it says, “oh yes, to make that work you need to do such-and-such - would you like me to do it?” Typing “yes” does the trick, and the it runs into another thing that doesn’t work but which it knows how to fix. And so on and so on, utnil eventually you get something that does work. But you can’t help wondering, if it knew all that, why it didn’t get it right in the first place.
When I had got it all working I asked my son, who works with React all day long, to take a look at the React code it had generated. His verdict: “It's sort of the right shape for React but quite brutally ugly”.
Now for the Details
I started by running Cursor in the directory containing all my C++ code, which it obligingly listed in a pane on the left side of the screen. Then I said something like “create a web front-end that accesses the functionality of my Wordle program here”.
It hummed and whirred for a bit and then produced some React code for the front-end. Then it said, “would you like me to create the back-end too?” Well, yes, that was the whole point. Over the next few days I got really good at typing “yes”.
The result was a bit of a surprise. It used a package called Crow to build a Rest API to its very own, very basic Wordle implementation, completely ignoring my code. That didn’t bother me too much - I expected to have to figure out an interface to the real back-end code that understands Wordle and all the complex algorithms involved. It took a couple of hours of refactoring my C++ code and inserting appropriate hooks in my new Cursor-created web_server.cpp file.
Then followed a seriously frustrating session. From the browser I could create a new game, but every attempt to interact with it just completely blocked at the browser end. It took a while to figure out that this was due to an arcanity called Cross-Object Resource Scripting (CORS). I confess to not fully understanding what this is, but the way Cursor had used React meant that it had to work. And it didn’t.
I spent ages googling, and trying to get Cursor to understand the problem and fix it. But it just kept going round in circles, telling about all kinds of completely irrelevant possible solutions. When a browser sees that a CORS interaction is about to happen, it sends a special OPTIONS request to find out whether the target is willing to support it. It’s just a question of adding a couple of text lines to the HTTP header. But whatever I did, these magic lines absolutely would not appear in my server’s HTTP response.
Finally, after a couple of hours, I discovered why, in the Crow documentation (which of course I hadn’t read before). Turns out that Crow refuses to let you modify the response to an OPTION request. I’ve no idea what the logic behind this is, but it makes it useless for this particular use case.
So… I said to Cursor, “generate the server for me using a package other than Crow”. It hit upon another package called Pistache, and generated the code to use that instead. The next hour or so gave me my best “typing yes” practice. The initial implementation wouldn’t build. Cursor identified an incompatibility between the code it had written and the version of Pistache it had installed. It then alternated between fixing its own code, and trying different versions and builds of Pistache. We went round that loop about 20 times before finally getting Pistache to work.
It turns out that Pistache is perpetually at version 0.99.xx, with the 'xx' varying on a daily basis and with an advertised total absence of backward compatibility. But in the end, it did work. I have absolutely no idea what I would have to do if I wanted to re-create the build environment.
Very quickly after that I had a rudimentary Wordle page that worked the way I expected it to, which was a pleasant surprise. I had spent maybe four hours of actual work, including the frustrating session with the useless Crow library.
Extensions
Now it was time to add all the extra functionality I wanted. The first things I added were how many possible words remain, and some suggestions for the best next word to try. Both can be turned on or off via respective checkboxes. Cursor did a great job here. It generated both the front-end and the back-end code and it took only a couple of iterations to get it to work properly. I was duly impressed.
The next item went much less well. The Wordle guesses are shown in a grid, with one word on each line, each cell coloured appropriately based on whether the letter is in the right place, the wrong place, or doesn’t appear at all. On the New York Times phone app, you type directly into the next, empty line in the grid. But Cursor had created a separate text box where you type, which then gets copied into the grid based on the result delivered by the back-end.
That’s functional, but not very pretty. I told it to make letters appear directly in the grid. It thought for a while, and generated a bunch of code. Nothing worked. You typed, and nothing appeared anywhere. It had explained that the text box was still there, and you’re still typing into it, but it’s hidden, and there’s code to move the letters into the right place as they are typed. Fair enough, if it works - but it didn’t.
It took a lot of iterations to get this to work as it should. But still, the letters weren’t being coloured based on the result, which defeats the whole purpose. And in the middle of trying to fix this, Cursor got stuck, running at 100% CPU and doing nothing. It often takes a while to figure stuff out, but this lasted for over an hour. I terminated it and restarted it. Coincidentally or not, soon afterwards it said that the log was too long and I would have to delete it to continue.
Left with no choice I did exactly that. But it wasn’t a good move. All the prior context of what it had done was lost. When I now said that the colouring wasn’t happening. it went off on several tangents to do with the code that colours CLI output, which is completely irrelevant to the web version. That was a very frustrating 10 minutes of saying “it’s your code that’s broken, nothing to do with this other class.”
Finally, in an oops moment, it realised it had changed the front-end code to expect a vector of guess results, but hadn’t changed the back-end accordingly. Once discovered, it was easy to fix. But it had taken most of an afternoon to get that far.
I once had a colleague a bit like that. He was a smart engineer and most of his work was good, but he would invariably break something, somewhere. When I pointed that out, he was very apologetic and rushed off to fix it - promptly breaking something completely unrelated. After he had left our company, his legacy included the most subtle, complex Heisen-bug I have ever experienced. It took over a week to find, finally done by reading every single line of every commit he had made in his final year with us. And even then it wasn’t obvious. Working with Cursor gives me a similar feeling.
After that unpleasant interval, though, things went amazingly well. I wanted to add a picture of a keyboard, with letters known to be inapplicable greyed out. I told it exactly that, no more, and it got it right first time. Even the aesthetics were good. Then I asked it to make the keyboard clickable, as a way to enter letters instead of typing them on the keyboard. That also worked perfectly first time.
Having got that far, I decided to pause and write about what I’d down, before I forgot it all. There is still plenty left to do. My first experience of vibe coding has been pretty positive. In a couple of days I’ve done something that would have taken weeks, and which in practice I would just never have done.