Version 0.8.2
Last release has fewer new features, but a lot of under-the-hood improvements and small tweaks. After vibe coding the entire app in 2 months, the day has come to pay the technical debt accumulated during experimentation and figuring things out.
I spend a few full days untangling the hairball that the model has managed to produce, more or less because of my own lack of understanding the full picture while I was developing it.
The AI model works differently than a human. A human programmer, will try to adapt the program architecture as new features are added, centralize and reuse code, refactor to accommodate the new requirements, etc. But the models don’t really do this. As the codebase grows, the model sees less and less of the entire app when it does changes.
While to a human it might be obvious that an operation is very similar to this other operation and you could create an abstraction to handle both, the model will happily re-implement the whole thing, usually in a completely different way.
For example, for tracking image generation, I had the following fields:
characterImageModelRequestId,
locationImageModelRequestId,
shotImageModelRequestId,
characterImageError,
locationImageError,
shotImageError,
shotVideoError.
This is the consequence of how I implemented them as I thought these features into existence. The model didn’t see the pattern (and neither did I at the time), so it happily implemented different paths for making the requests, polling the results and updating the UI.
Then, each object had different field names:
characterId, imageFileName
shotId, filename
locationId, imageId, image.filename
Each object kept a reference to the image in a different field.
The prompts used to generate the images were stored in various fields:
character.prompt,
location.image.prompt
shot.prompt
shot.videoPrompt
Each entity had a different naming scheme for the image filename, requestId, error and prompts. Thus, the different implementations that the model created for each entity type.
As I said, I don’t completely blame the AI models for doing this, because this was the result of building from scratch and experimenting a lot, changing my mind.. But as my understanding of the data structures deepened so did my discomfort with the code that I had at hand.
So I had to consolidate all of that.
Image: { filename prompt: {prompt used to generate image} label //used for locations }
Centralized polling for all entity types, UI updates via model only.
One of the biggest hairballs was PollingManager.js, a file I’ve spend several full days on, pulling and unifying the state management, UI updates and network polling.
I’ve managed to reduce the boilerplate quite a bit, even though I’m still not entirely happy with the architecture, it’ll do for now.
Vibe coding might sounds like a relaxed and fun activity, but realistically, it requires constant thinking both when implementing new features and when maintaining the code.
Up to a certain codebase size, the models shine - and it really doesn’t matter what the architecture is as long as it’s working.
When the amount of code grows, bad architecture becomes the bottleneck and slows you down (and the models) considerably. Even if the app works well, the internals make it impossible to safely add new features and ignoring the architecture will only make it even more entangled.
What did I learn ?
One, data structure choices dominates everything. Inconsistencies in data structures leads to a lot of additional boilerplate.
Second, architecture reviews are essential early on. When vibe coding, dedicate one day a week for architecture review and revamping. The more you postpone, the harder it is to fix it.
Anyway, I’m continuing my quest to improve the quality of the code and also working on a new feature - agentic animation, which is so cool I can’t wait to work on it.
Stay tuned and bring some smiles on people’s faces !