I Deleted My Own Feature by Accident, Then Rebuilt the Whole Database Around Strangers

U-PRO Build Journal — Episode 1

This is the first entry in a series about building U-PRO, a shop-floor management system for auto repair garages, alone, from the actual commit history — the decisions, the mistakes, and what they cost. It starts here, with the night the database itself had to change shape.

There's a specific kind of panic that hits when you realize you just wiped out 835 lines of code you wrote yourself, on purpose, thirty minutes ago — and you don't fully understand why it happened until you're staring at the diff.

That was my Tuesday night in mid-July. I was three weeks into building U-PRO, entirely alone. No co-founder, no engineering team, just me and a growing pile of decisions nobody was going to double-check for me. I'd just merged in some navigation fixes from the main branch — small stuff, arrow keys in a photo viewer — and somewhere in that merge, the edit page for car records had picked up code from an authentication branch I hadn't finished yet. The page was broken in a way that wasn't obvious from looking at it. It just had the wrong version of itself layered underneath.

So I fixed it. Then I fixed it again, because the first fix wasn't quite right. And the second fix, in the process of "restoring the correct version," deleted an entire platform-admin module — API routes, the admin page itself, a helper library — nearly 600 lines that had nothing to do with the bug I was chasing. Gone, in a single commit, because I was moving fast and trusting a merge tool to know what "correct" meant.

I caught it about a minute later. Reverted it immediately. But that minute is the part I still think about, because it's the moment I understood something about working alone that no amount of reading about software engineering had prepared me for: when you're the only reviewer of your own work, the gap between "I made a mistake" and "someone points out my mistake" collapses to zero. There's no second pair of eyes. There's just you, noticing, or not.

Hands frozen above a keyboard in a dark home office, laptop screen showing a dense red-and-green diff

The bigger problem hiding behind the small one

A glowing database structure cracking apart into separate isolated chambers

That merge accident wasn't really the story. It was a symptom. The actual story is what I was building toward that entire month, and why it made nights like that one basically unavoidable.

U-PRO started as what you'd call a single-tenant system — one shop, one dataset, one implicit assumption baked into almost every query I'd written: that whoever was looking at this data worked at the shop, because there was only one. That assumption was fine for the first few weeks. It let me move fast. I didn't have to think about data isolation, permission boundaries, or who-can-see-what, because there was only one "who."

But the entire premise of U-PRO is that it works for many shops — different owners, different teams, different customers, none of whom should ever see a byte of each other's data. That meant the single-tenant shortcut had an expiration date from day one. Every week I kept building features on top of it was a week of debt that would need to be repaid in one enormous, invasive operation: ripping out the "there is only one shop" assumption and replacing it with "there are many shops, and the database has to enforce the wall between them."

That operation is what the commit messages from that stretch of July call, plainly, "migrate production to full multi-tenant system (auth, jobs, customer portal, reports)." Not a clever name. Not a codename. Just a literal description of ripping the floor out from under a running system and putting a different floor in its place — one wired for authentication, job records, a customer-facing portal, and reporting, all of it now aware that "which shop" was a question every single query needed to answer.

If you haven't built something like this, "multi-tenant migration" undersells how invasive it is. It's not one flag you flip. Every page listing car records needs a new filter — not "show me the cars" but "show me the cars that belong to the shop this logged-in person actually works at." Every form that creates a job needs to stamp which shop it belongs to, correctly, every time, because getting it wrong doesn't throw an error — it just quietly attaches someone's repair job to the wrong garage. The login system itself needs to know which shop you're allowed to see before it shows you anything. Auth, job records, the customer portal, reporting — all four had been built on the assumption that this question didn't need asking. Retrofitting it into all four at once, in a system already holding real data, is what that block of July was actually spent doing.

What "migrating in place" actually looked like

I want to be honest: this wasn't a single dramatic all-nighter ending in one triumphant commit. I wish I could tell it that way — cleaner story. What actually happened was messier, and I think truer to what solo, fast-moving development looks like.

The first real landing of the multi-tenant migration happened on a Thursday afternoon in mid-July — not glamorous, not at 2 AM, just a regular workday where a commit titled "migrate production to full multi-tenant system" went in carrying over a hundred file changes and eleven thousand lines. But that wasn't the end of it. The exact same commit message shows up again four days later, on the following Monday morning. And then again. And again, at least half a dozen times across that week, each one syncing another slice of the multi-tenant work from my staging environment into what production was running.

At first this felt mildly embarrassing to admit — shouldn't a "migration" be one clean cutover? But the repetition itself turned out to be the honest lesson. Multi-tenancy isn't a feature you ship; it's a property that has to be true of everything — every query, every API route, every page that renders a list of records. You don't finish migrating to it in one commit any more than you finish "being careful" in one. You keep finding corners of the codebase that still assume there's only one shop, fix them one at a time, and keep going until you stop finding corners.

And some of those fixes broke things that were already working. One sync in that batch quietly reverted three features that had nothing to do with multi-tenancy at all — a company name field, a required-photo alert, a small UI element showing who was logged in. Not because anyone touched them on purpose. Because when you're merging a large branch back into a codebase that's kept moving without it, things get silently overwritten in the noise of a big diff. I didn't notice until later that same day, when I went looking for those features and they weren't there. I had to write a follow-up commit specifically to bring them back — restoring things I'd already built, because a bigger, more urgent piece of work had trampled them on its way through.

If there's a single image that captures that week for me, it's not "heroic engineer migrates database at 2 AM." It's closer to: patching a boat while it's still in the water, discovering a new leak every time you fix the last one, and slowly accepting that this is just what the job is when there's no one else to hand it to.

Weathered hands pressing a patch against a cracked wooden boat hull at night, lit by a single lantern, while the boat still sits in dark water

That's a specific kind of exhaustion build-in-public writing doesn't talk about much, because it's not dramatic enough for a good headline: not one brutal night, but a week where you think you've found the last corner every single day, then open a different page and realize it still thinks there's only one shop in the world. By the middle of that week I'd stopped saying "this is the last piece," even to myself — I'd been wrong about that too many times for the sentence to mean anything anymore.

Why I didn't wait until it was "safe"

A lone silhouette standing at the edge of a narrow catwalk suspended over a dark void, one hand resting on a control lever

Here's the question I'd ask myself if I were reading this from the outside: why migrate a running system to multi-tenant architecture with users already on it, instead of building it right the first time, or at least building it in a separate environment and cutting over cleanly?

"Building it right the first time" was never on the table, because I didn't know what "right" looked like until I'd built the wrong version first. The single-tenant version wasn't a mistake — it was how I learned what the shop-floor workflow actually needed before trying to make it work for many shops at once. I wasn't rebuilding from a blank page. I had a reference implementation sitting right next to me.

As for a clean separate environment with a scheduled cutover — that's the version of this story where I had a team and a maintenance window. I didn't. What I had was a codebase, a handful of early users, and a backlog of multi-tenant work that got more expensive the longer I put it off. Waiting for "safe" mostly meant paying interest on a debt that compounded weekly.

So I migrated in place, in production, with no rollback plan beyond "revert the commit and hope." Not a strategy I'd recommend to a team with an SLA. It's the strategy of someone building alone, fast, where the alternative — freezing feature work for weeks to build a parallel system — would have killed the project's momentum before it had customers to lose.

There's a quieter reason too: in mid-July I genuinely didn't know how many shops would use this or how fast. A tested rollback script and a scheduled cutover window pay off once you know the system needs to survive dozens of businesses depending on it daily. I didn't have that certainty — just a hypothesis and limited time before I'd need real signal on whether to keep building. Spending a week on migration infrastructure for a scale I hadn't proven I'd reach would have been solving a problem I might never have.

What almost went wrong, and what didn't

I'd be lying if I said nothing broke. Things broke. The silently-reverted features are proof of that. There's a version of this story where that kind of silent regression happens somewhere more dangerous — a permission check quietly reverted instead of a UI label, data isolation between shops accidentally weakened during a merge instead of restored the same day. That didn't happen this time, but I can see exactly how it could have, and that's stayed with me since.

What actually saved me wasn't a rollback plan. It was smaller and less impressive than that: I kept looking. I went back through the app after each sync and checked what was actually there, not what the commit message said should be there. That's how I caught the reverted features within hours instead of weeks. It's a weak substitute for proper pre-deployment verification, and I knew it at the time. But it was what I had, and it worked well enough to get through that week without anything user-facing staying broken for long.

The bigger thing I took from that stretch of July wasn't about databases. It was about the shape of risk when you're building alone. Every "obviously reckless" decision that month — merging without full review, migrating production without a tested rollback, stacking fixes on fixes at ten at night — was reckless by the standards of a team with more hands and more time. It wasn't reckless by the standard of getting something useful in front of real shop owners before the runway ran out. Confusing those two standards is how solo builders either freeze completely or burn out chasing a bar built for resources they don't have.

That trade-off doesn't end after the first month — it just changes shape. Three weeks in, the risk was deleting something by accident and not noticing. A few months in, it gets harder to catch by looking around the app, because it's no longer about whether a feature is visibly there. It's about whether the walls between shops actually hold under pressure I haven't tested yet.

And that's exactly the question the multi-tenant migration opened up, the one I wasn't fully prepared to answer that week even after the migration had technically landed: I'd just made the decision to put data from different, unrelated shops into the very same database, trusting a wall of permission checks to keep them apart instead of the much simpler wall of "just use separate databases per shop." On paper, separate databases sound safer — a mistake in one shop's data can't possibly leak into another's if they're not even in the same place. But separate databases also mean separate migrations to run, separate backups to manage, separate everything to keep in sync, multiplied by every shop that signs up. That's a cost that scales badly for exactly the kind of business I was trying to build — one where a small garage should be able to try the system without me needing to spin up dedicated infrastructure just for them.

So I'd chosen the shared-database path — isolation enforced by code and policy, not physical separation. Partly because it was faster to build on what already existed, partly because I believed it was the right long-term architecture for scaling to many small businesses cheaply. But belief isn't verification. "I chose the cheaper wall" and "I've proven the cheaper wall holds" are two different sentences, and that gap is exactly where Episode 2 picks up.

Was trusting that wall the right call, or just the fast one? I didn't know yet. What I did know, standing at the end of that stretch of July with a system that finally treated "which shop" as a real question instead of an assumption, was that every other architecture decision from here on would be built on top of this one — and there was no version of the next six months where I got to unmake it cheaply.

A weary figure resting in a chair as pale dawn light begins to break, beside a now-quiet laptop and several sealed, stable chamber-like structures


This is Episode 1 of the U-PRO Build Journal — a real-time account of building a shop-floor management system for auto repair garages, written from the actual commit history and decisions made along the way. Episode 2 looks at why shared-database multi-tenancy won over the safer-looking alternative of giving every shop its own database, and answers the question this episode leaves open — what U-PRO's data-isolation model looks like today, not just what it looked like on that first night.