I shipped CodeMaster. Then I learned that shipping was the easy part.
For the last two months, I've been in maintenance mode. Refactoring. Rewriting. Breaking things, then fixing them, then breaking them again. The features all work. The app is live. But the codebase underneath? It was a mess I had to clean up.
This is the part no one talks about when they say "just ship it."
The 150-Commit Branch
Most of my branches have one to three commits. Small, focused changes. But there's one branch in my git history with 150 commits. I call it my shame branch.
Here's what happened: I added a linting rule that enforces file size limits. Overnight, I had 180+ warnings. Files that had grown too big while I was focused on features. So I thought, "I'll just fix all of these at once."
That was a mistake.
I started extracting helper functions, splitting files, reorganizing folders. Every change broke something else. I'd fix one regression and create two more. I was refactoring code I'd written when I didn't fully understand what I was building. The 150 commits were me stumbling through the mess I'd made.
When You Move Fast, You Leave Messes Behind
I used AI to build faster. ChatGPT and Claude were my constant companions. But building fast with AI created its own kind of debt.
I didn't write tests as I built. So later, I asked AI to write tests for bugs I kept hitting. But generating tests isn't the same as understanding what to test. I ended up with tests that were too tightly coupled to implementation details. When I refactored, the tests broke, and I couldn't tell if it was a real bug or just a test that needed updating. I had to throw a lot of them out.
Same with linting. At first my rules were too strict. AI is good at delegation, but if you're not reviewing the output carefully, the quality slips. While the foreman is out, the workers go out to play.
Every shortcut I took came back to haunt me.
What I Should Have Done
I set up linting rules and testing infrastructure on the same day back in July. But I didn't enforce them consistently. The rules existed, but they weren't blocking bad code from getting merged.
Now I start every new project differently:
- Linting rules from day one, enforced on every push
- Tests written alongside features, not bolted on after
- Small, focused refactors instead of trying to fix everything at once
I also learned that tests should verify behavior, not implementation. The question isn't "did it call this internal function?" It's "did it return the right data?" If I'd understood that earlier, my tests would have survived the refactoring.
Building vs. Keeping
Building something and keeping it running are different skills.
When you're building, you're focused on making it work. Ship the feature. Fix the obvious bugs. Move fast. But maintenance requires a different mindset. You have to think about what happens six months from now when you've forgotten how this code works. You have to care about the code you're leaving behind, not just the feature you're adding.
I'm considering converting the project to TypeScript. Not because JavaScript doesn't work, but because TypeScript catches the exact bugs that kept biting me. Wrong data shapes. Missing properties. Functions returning different types than their callers expect. The compiler would have caught half my regressions before they happened.
The Lesson
Everyone celebrates shipping. But the project isn't done when it ships. It's done when you abandon it.
I spent two years building CodeMaster. I've now spent two months just cleaning it up. That ratio probably isn't unusual. The maintenance phase is long, unglamorous, and necessary.
If you're building your first real project, know this: the code you write today is code you'll have to maintain tomorrow. The shortcuts you take will come back. The tests you skip will haunt you.
Ship it. But then stick around for the hard part.