It’s probably no surprise to anyone that Tomás the Purrfect Budgeting App is being coded with a good amount of AI, in general I could say is just the way it is now for every development.
In this post I would like to talk about our approach to it, the problems we had, how we solved them, what it allowed us to make, an in general how good the vibe really is.
1.- Ethics of the vibing
It is probably strange to see a programmer that is extensively using AI to code to talk about ethics, but I do believe it’s important.
AI is basically trained in all the corpus of Open Source out there, I say Open Source because I really don’t think AI companies would be able to take a look at the latest code from Adobe, and definitely they wouldn’t be able use it for training.
There is of course this thing where AI advocates say that it is no different that a person learning from such openly available resources, but when LLMs can regurgitate line by line parts of such code, its clear at least for me that yeah, some way or another we are creating our apps out of open source software.
The magic thing here is that, in reality most if not all of OpenSource licenses actually want you to make money out of it, they only differ in the amount of recognition given to the original creator and if you should publish your work back as open source.
I cannot do much about the recognition part as the specific influences are hidden deep inside the neural network, but at least, unattached from financial and legal limitations with investors or employers, I can publish Tomás as Open Source.
On the business side, it’s simple: offer SaaS hosting for those who don’t want to self-deploy, plus enterprise support and premium AI-powered services like receipt processing that require server infrastructure.
2.- AI is an over enthusiastic Junior… with ADHD
At the beginning it only had chat capabilities and the VSCode developers patched a way to apply the suggestions from the chat window into code, it was functional but slow.
Now we have Agent mode and it can find its own references, run commands, move files from one place to another, and in general do everything you can do, except maybe pushing to prod…
I have found that AI really is like a regular ADHD dev that makes mistakes, leans into assumptions, keeps repeating old patterns because that’s how it learned, gets easily sidetracked by refactors, and doesn’t know where to cut a PR so it just keeps adding to it.
And the solution to most problems caused by an ADHD dev is just good development habits, the same ones that you should already have but that you forgot to implement because suddenly the AI goes too fast and felt that if you can “skip” coding, you could also skip on everything else.
3.- Good Development Habits
So lets tackle each one of the issues caused by lack of development sanity and the corresponding good practice.
3.1- Commit every time a small goal is achieved
I personally used to commit a lot and don’t care about having a clean log for development branches. Because you can always squash merge on the PR and leave a clean history in main/develop, no code is made without breaking a few builds.
But now with copilot I’m finding myself committing every successful prompt. And that has saved me a lot of times.
AI has a tendency to go in destructive refactors when it can’t make a test pass or it encounters an error. It suddenly decides the solution is to go into full refactor mode, or to totally delete a file and start from scratch.
It’s in these moments when you will want to revert all changes and start again from zero. But you can only do that if you commit regularly.
3.2.- Work incrementally in small iterations
Don’t expect Copilot to solve a full system in one prompt. While it could definitely do it with the proper prompt, you won’t know until it finishes if it was a success, and then you will be left with a lot of code to read, test and carefully sanction… because you do read what the AI creates… right? RIGHT!
The problem is, the more you have to review in one sitting, the more chances there are that you overlook something.
Treat vibing like you would treat actual coding: don’t ask the AI to modify the API, the unit test, the frontend and the UX test all at once. Tackle each individual part of the problem in its own step*, validate and move to the next one. This will allow you to think about every piece.
3.3.- Build persistent context
AI models have a limited context window. This means they often revert to generic solutions instead of following the instruction you gave 20 messages before, simply because it forgets about it… funny enough we humans also have bad memory and forget about previous decisions.
For both AI’s and your own sanity, get in the habit of creating reusable, persistent context in the form comments and documentation files. The AI will work better when it has more permanent context to work with and you and your coworkers will have something to refer to next time someone forgets exactly what shade of white was chosen for the headers.
3.3.1.- Build permanent context with copilot-instructions.md
Create a .github/copilot-instruction.md
file, and start documenting how you want things done. Each time copilot gets something wrong from your prompt, take your time to include it in the copilot instructions.
For example we have:
- How the roles work - The AI kept giving superadmins permissions to do everything when we wanted the superadmins to not have access inside the workspaces.
- Specifics on what versions of software we are using and how it works, so it doesn’t implement old patterns from deprecated libraries.
- Details on how we like to split responsibilities inside the code.
- We had developed a few common components for common things, like an input designed to handle currency and basic operations, that input should be used whenever we are handling currency.
- To put all styles in a main styles.css file instead of adding scoped styles in each component.
Without these instructions copilot kept forgetting to implement things OUR way and reverted back to either the most common way or the easiest/laziest way.
Is our way the best? Maybe not, but at least it’s consistent, and that is more important.
3.3.2.- Add comments and documentation to files
Get in the habit of adding comments and documentation to your files (or ask the AI to do it for you). The more documented your code is, the better the AI will understand what it does and how to work with it.
And once you are there, get in the habit of reading the comments on the code generated by AI so you also know whats is happening behind the prompt.
3.4.- Split long files into smaller ones
AI will recreate similar components or functions in multiple places rather than creating reusable, modular code, just because it’s easier or doesn’t remember we already had a component made for that.
This has the unwanted effect of making AI slower as it has to read long files to know what they do and to apply changes. The smaller the function/component the AI is working on, the faster and better it will work.
We are all guilty of this sin of course, I vividly remember my days of copy&pasting as it was yesterday and it probably was TBH.
So every step of the way be alert of files growing too big and find patterns to split them into smaller ones
Examples of this are:
Utility functions starting to appear? - move them to its own library.
A Section of your UI grows too big? - move it to its own component.
Your components folder is getting cluttered? - look for patterns and create subfolder by type (inputs, navbars, layouts, etc…).
Too many routes? split them by domain/category.
The benefits of this are:
AI will take less time processing.
A Clear set of input/outputs simplify troubleshooting.
Smaller elements are easier to write unit testing for.
You and the AI can forget about what is behind that smaller files and treat them just as an abstraction, freeing context for the current problem.
Increased reusability of the individual code pieces.
3.4.1 AHA! Avoid hasty abstractions
About this I’d like to add a concept that really hit home when I read about it a few years ago: AHA programming. I’m pretty sure you’ve heard about DRY (Don’t Repeat Yourself), which this whole section basically advocates.
But there’s another programming catchphrase that says “Premature optimization is the root of all evil”. Don’t start making thousands of subfolders or get ahead of yourself creating different utility libraries for each domain. Wait until the patterns emerge, or risk cluttering your code even more.
In that AHA article, the author introduces the concept of WET (Write Everything Twice), and goes with the following rule:
You can ask yourself “Haven’t I written this before?” two times, but never three.
In general, what you want is to allow yourself some flexibility - observe before you act.
“You must see with eyes unclouded by hate. See the good in that which is evil, and the evil in that which is good. Pledge yourself to neither side, but vow instead to preserve the balance that exists between the two.”
Princess Mononoke (1997)
3.5.- Teach the AI modern patterns
AI suggests outdated patterns because:
- It’s trained on older code that’s more prevalent in open source repositories
- Their information cutoff is always in the past
Of course, we are also subject to such outdated knowledge. The cynics in the room will say that “All code becomes legacy the moment it is released to production” (that’s me, I’m the cynic in the room). But the optimist will say that yes, but you can be one month legacy or 5 years legacy (that’s my partner who is also coding this project).
So it’s always good practice to use the latest fully supported version of your dependencies. In our case we had these 3 problems:
Vue Options API vs Composition API
Copilot created all the first components using the Options API instead of the newer Composition API. I didn’t know the difference at the beginning because the last time I used Vue was with Vue 2. Once I realized this, I had to research the differences, correct the AI, and migrate everything to the Composition API.
Storybook 9 struggles
Copilot really struggles with Storybook 9. I don’t know if the AI’s knowledge cutoff is before Storybook 9’s release date, but man did I have to fight that battle uphill. Once I got everything working and left a couple of fully functioning Stories with unit testing, we instructed the AI to follow those examples and it worked just fine.
Bootstrap dark mode confusion
Bootstrap 5.3 introduces simple dark mode where all you have to do is set up the data-bs-theme
attribute on main or body elements and use bg-body
, bg-secondary
and bg-tertiary
for backgrounds, which automatically adapt to the theme.
Well, Copilot kept insisting on a complete rewrite of all the CSS variables, effectively duplicating what Bootstrap already does, and kept using bg-light
and bg-dark
for all backgrounds.
The trade-off
You have a decision to make: if you go back a few versions, you’ll benefit from much larger training data, but you’ll miss the benefits of newer versions of your favorite tools.
I have no doubt that if I had chosen Storybook 8, Copilot would have made everything perfectly for me, but then again, Storybook 9 is better.
Our approach: Create working examples, then teach
- Research and implement the modern pattern manually (or with lots of struggle)
- Create a few solid working examples
- Document the approach in your copilot-instructions.md
- Reference these examples when asking AI to create similar functionality
Once AI has a working pattern to follow, it’s much better at replicating modern approaches than trying to figure them out from scratch.
3.6 Build it and they will run them (the unit test).
There’s no better time to introduce Test Driven Development than right now when working with AI.
In TDD you create your functions and components along with a set of tests that define the expectations for the code, then with that tests already in place you can make your continuous integration run the tests and deny any merge to main/develop that doesn’t pass them.
This is crucial because AI has a tendency to change implementations silently. If you’re not careful, you’ll lose functionality that was already working, or the AI will change the implementation to something you don’t want.
But with TDD, the tests will break and the AI will work to fix them (yeah, in Agent mode it even runs the tests by itself).
This used to be heavy work, but with AI is suddenly achievable for small teams or solo developers, which brings me to the next topic.
4.- Where vibing got us
The current team of Tomás is me (levhita, fullstack web developer) and my partner (Sol/Meme, Frontend). In our time in the industry we have been in big companies that do the whole stack of good practices, and we are used to doing all of that, but of course with enough time, fully paid, and not usually wearing all the hats at the same time.
As much as I like to do everything from devops to frontend, it’s just not realistic to do that in a job. It’s nice to work with someone who understands the whole process, but my employers and teammates don’t expect me to do everything.
But with personal projects, you’re expected to do it all, and in the process you usually don’t do things that are boring or don’t bring direct benefit to the project, simply because you don’t have time (did I mention we have a toddler?).
But with AI, we can suddenly implement all the good practices and actually run a project with all the care it deserves. Things that we were able to implement because we have AI are:
- Fully separated API and Frontend (we can horizontally scale the servers in no time).
- Complete Continuous Integration process with actually working unit tests and deploy to our demo server.
- A proper blog, also with continuous integration.
- Proper Login, and separated areas for workspaces, home and admin section.
- Carefully crafted core experience, with equally crafted surrounding experience.
- Visually appealing design, at least better than most of my previous systems.
We even had time to create a nice looking logo with variants. This was not thanks to the AI, but it was certainly good to have peace of mind about the core development going well.
5.- Conclusion
This project wouldn’t be possible at this scale without AI and think that is just fine, I do foresee that we might be entering in a golden age of Open Source development, and that every single dev out there with an idea is about to embark into this addictive code frenzy to see it become reality.
But in this ocean of new code that is about to hit shore, we should be careful in what we return back to the waters of Github, remember that is going to be used to train the new models, don’t commit shit.
Not because of the damage we could make to our corporate overlords precious models, but because I think we should embrace the opportunity to extended our own capabilities and create better more comprehensive systems.
~ Levhita
PS: Corporations will use AI to create all short of horrible things, and they wont stop just because you might disagree with the model, better take advantage of the same tools to do some good.
I specially like this piece by Cory Doctorow about being careful when taking sides in the AI internet fights: Neither the devil you know nor the devil you don’t . It’s a long read, but is worth it.