From a Weekend Idea to a Shipping Product
Smara Account started from a real, personal itch, and turned into a learning exercise almost by accident. I wanted to understand how far I could get building a real application almost entirely in conversation with AI, with no fixed idea of where it would end up. A few months later, it's a working, tested, multi-platform app, translated into forty-three languages, with a real release process behind it, that I actually use for my own accounts. This post is the short version of how it got from one of those states to the other, told from what I personally learned along the way — not a controlled study, not a claim about how any team or any other AI setup would fare, just one person's data point. The rest of this series goes deep on the specific moments that mattered.
Why this, specifically
I've used personal accounting software for years to track my own spending. It isn't a simple picture: multiple cards, multiple currencies, accounts spread across different countries. Plenty of existing tools cover that ground reasonably well on features. Almost all of them are cloud-based, subscription-priced, or both — I never liked handing my financial history to someone else's server as an ongoing rent. More importantly, as far as I could find, none of them make any real claim about the integrity of what they record. Once an entry is in, is it actually protected from being quietly changed later — by the software, by a bug, by anyone? It wasn't something any of the tools I evaluated treated as a first-class concern — which doesn't mean no such tool exists, only that I hadn't found one.
I'd had the idea of building something better for a long time, and had started on it more than once. Each time, it ran into the same wall: doing it properly meant learning a new language and a real application stack from scratch, or falling back to the same shape every other tool already had — which defeated the point of building it at all. The idea kept getting shelved, not because it was wrong, but because the distance between "I know what I want" and "I know how to build it safely" was large enough to keep stopping me.
What changed wasn't the idea. It was framing the distance itself as the actual challenge: with real technical knowledge, but without formal training in building this specific kind of application, how far could I get toward something that could genuinely run in production — using AI as the thing that closes that gap, rather than years of learning a new stack first? That question, more than the accounting problem itself, is what actually started this journey.
Where it actually started
The first stretch of this project was what I'd call vibe coding: describing what I wanted in plain language, letting the AI write whatever it thought that meant, running it, and iterating by feel. This is a genuinely good way to explore an idea. It told me quickly whether a feature concept even made sense, without spending real design effort on something that might not survive contact with reality.
It's also a way of working that quietly stops scaling the moment the project has more than a screen or two worth of behavior to keep straight. There was no fixed record of what the app was actually supposed to do, so there was no way to evaluate whether a given change was correct — only whether it looked right in the moment. Regressions crept in unnoticed. Explaining context to a fresh conversation, or to myself a week later, got slower every time. It became genuinely painful before I admitted that the approach itself, not any specific bug, was the problem.
The turn
Around that point, a training session at work happened to be about spec-driven development — writing down what a system should do, in a structured way, before writing the code that does it. I'd seen the idea before in the abstract; this was the first time I connected it directly to the pain I was already feeling on this project.
What I actually learned went further than that one practice. Alongside spec-driven development, I found myself relearning architecture as a guardrail, design as a guardrail, and a required development approach — test-driven development, specifically — as a guardrail too. These aren't new ideas. What was new was realizing where this knowledge normally lives: mostly in the heads of a handful of senior engineers on any given team, passed on through review comments, hallway conversations, and osmosis, rarely written down in full because a human colleague can absorb most of it without a document. Working with an AI collaborator removes that shortcut entirely. It has no tenure on the team, no hallway conversations to have absorbed, nothing to fall back on except what's actually written down. For the first time, I had to take knowledge that normally stays implicit and put it into an explicit document, in real depth — not because the AI demanded it, but because nothing less was going to work.
The actual loop I settled into is small and repeats for every change, large or small — but the loop by itself is just a shell. What actually points it somewhere useful is architecture and design shaping what gets proposed, and a required development approach shaping how it gets built:
flowchart LR
AR["Architecture guardrail<br/>where this belongs<br/>in the system"]
DS["Design guardrail<br/>how it should work,<br/>decided before how it's built"]
TD["Required approach<br/>test-driven development"]
A["Propose<br/>write the why, the design,<br/>the task list"]
B["Apply<br/>implement one task<br/>at a time, verify each"]
C["Archive<br/>only once every<br/>task is checked"]
AR --> A
DS --> A
A --> B
TD --> B
B --> C
C -.->|next change| A
(The fourth guardrail — UI-driven acceptance testing — gets its own diagram further down, once the testing stack it belongs to has been introduced.)
This isn't the only way to structure it, and I wouldn't claim it's the best one — it's just the shape that ended up making a difference once I started following it consistently.
Writing it down is not the same as it being followed
Here's the part that surprised me most, and it's worth saying plainly: even with all of that written down, the AI does not reliably follow it. Most of the time it does. Often enough, it doesn't — and it doesn't fail loudly or ask for clarification when it skips something, it just quietly proceeds as if the instruction weren't there.
I saw this most starkly once, on a separate codebase I was experimenting with around the same time. I asked directly for test-driven development — write the failing test first, then the code. What I got instead was code. Straight ahead, fast, confident-looking code, with no tests leading it and no tests following it, as if the instruction had simply been noted and set aside. It's a good image for what an unguided AI actually does: like a horse at full gallop with no blinders and no reins — genuinely fast, and headed wherever its own momentum takes it, not necessarily where you pointed it. The breakage only became visible once real interface testing started. Everything had looked fine right up until then.
That single experience reframed how I thought about instructions to an AI collaborator. An instruction in a document is a request, not a constraint. If a rule actually needs to hold, it needs a mechanism that enforces it, not a sentence that asks for it — the difference between these two paths turned out to matter more than almost anything else in this project:
flowchart TD
I["Instruction: follow TDD"] --> N{Guardrail<br/>or request?}
N -->|Just a request| W["AI writes the feature first,<br/>tests later or not at all"]
W --> X["Breakage only visible once<br/>something else happens to fail"]
N -->|An enforced mechanism| G["Commit is blocked unless<br/>format, analysis, and tests pass"]
G --> P["Discipline holds whether<br/>anyone remembers to ask or not"]
I want to be honest about what that mechanism actually guarantees, because it's narrower than it sounds: a hook that blocks a commit until tests pass proves the tests exist and pass at that moment — it doesn't prove they were written before the code, only that the code can't land without them. Real test-first discipline still comes down to habit. What the hook reliably closes is the worse failure I'd already seen: code with no tests at all, merged and forgotten.
Building the mechanism, one layer at a time
The mechanism I ended up building was a stack of tests, not a single kind, shaped like the classic testing pyramid: a wide base of fast, narrow checks, thinning out to a much smaller number of expensive, full-system ones — and then, above even those, two layers that aren't new test code at all, just different ways of running and trusting the same suite. Unit and widget tests cover individual pieces of logic in isolation, with their dependencies mocked. Integration tests check how those pieces behave together against a real database and real local state. A kind of business-acceptance test actually drives the compiled application through its real interface, the way a person would use it, and checks that the outcome is what the requirement actually promised. That same acceptance suite then runs automatically every night, once per supported locale, so a translation change can't quietly break a scenario nobody thought to check by hand. And before anything actually ships, a person runs that same suite once more by hand, in English, on a real Mac — the one check nothing automated replaces. Here's the actual shape of it, with real numbers attached, at the point this post was written:
The bottom three layers add up to close to a thousand automated checks in code, and that shape is deliberate: cheap enough at the base to run constantly, expensive enough near the top that thirty-seven of them earn their keep by covering entire real-world scenarios rather than single functions. The top two layers aren't more test code — they're that same acceptance suite multiplied across every locale automatically, and then confirmed once more by a person before anything ships.
This is the actual training session for that horse from earlier: not one command, but a stack of them, run against every single change until following the discipline stopped being optional.
This did not come for free. As the test coverage got more serious, a development-and-test cycle that used to take minutes started taking hours. That's a real cost, and I paid it deliberately, because the alternative — fast cycles producing code that only looked right — was the exact problem I was trying to get out of.
What made the cost worth it was what started happening once the pieces were all in place: I could let an agent run a full piece of work end to end, without stepping in along the way, and it would routinely introduce a real bug or two during that run — and then find it and fix it itself, before anything ever reached a pull request I'd need to review. That's a genuinely different working relationship than reviewing a wild first draft line by line. The guardrails weren't just catching mistakes for me anymore; they were letting the AI catch its own.
I want to be precise about what that claim covers, though: it's bounded by what the suite was actually built to check. I don't have a count of what slipped past it, only what it caught — a test suite proves the presence of bugs it finds, not the absence of the ones it doesn't. The honest version of this lesson isn't "the AI stopped writing bugs." It's narrower and still useful: a whole class of regressions stopped reaching me silently.
How the work actually split
In practice, I ended up using more than one AI tool for more than one purpose: research and detailed requirement-writing through Claude, and most of the day-to-day implementation through Cursor's agents, working from whatever had been specified. I'd assumed, going in, that simply routing everything through one strong model would be what brought the bug rate down. On this project, that isn't what happened: what actually moved the needle was the engineering discipline wrapped around whichever model was writing the code — the guardrails, more than the brand name. I can't rule out that a different project, or a genuinely much stronger model, would tell a different story. This is what I actually observed on the one project I have.
The lesson underneath all of it
That horse from earlier is really the whole lesson. An AI model is trained on an enormous amount of code, and a meaningful share of that code is not good code. Left with a vague instruction and no constraints, it's just as likely to confidently reproduce the bad patterns as the good ones — fast, capable, and unconcerned with which direction it's actually running. That speed is genuinely useful. It's also not something I'd want to simply sit on and hope for the best.
You don't get a horse like that to take you somewhere by asking it nicely — you train it, and you fit it with blinders and reins. Put the same kind of real guardrails around an AI collaborator — architecture, design, a required development approach, enforced mechanically rather than requested politely — and the same underlying model produced far more consistent results, at least on this project. I don't know how far that generalizes beyond it. What I can say is that on the one project I built, that distinction mattered more than which specific tool or model I used.
Where the story gets more interesting
Once the app was working reliably in one language, the next real test was scale: forty-three languages, nearly all of the translation work done by AI rather than a human translator for each one. That's where a whole new category of problem showed up — not "does the code work," but "does the same tested behavior actually hold once you change the words underneath it." That turned out to be its own long story, worth its own posts.
The rest of this series walks through the specific moments in more detail: the discipline that made corrections cheap, the bugs that looked like one thing and were three, the assumption about a testing platform that turned out to be completely wrong, and the very particular way AI-generated translations tend to fail. None of it happened in a straight line, and none of it happened without real setbacks along the way — but a weekend exploration did become a real, working app that I actually rely on for my own accounts, built to the same bar I'd have held it to if it were going out to other people. I think the path between those two points, and what it taught me about working with AI, is worth writing down honestly.
Try it yourself: Smara Accounting is in closed testing on Android — request access as a tester. The iOS and macOS versions are currently in App Review.